跳到主要内容

68.namespace

PR # 1564

This is the update to actually enable namespace support.【正式添加namespace】

At the moment, I pass a namespace as a query parameter in order to support namespace scoping on the v1beta urls. Once v1beta3 lands, I will modify to pass namespace in the path. Note the client libraries were modified to make this easy to do, so should be a trivial change once ready.

I broke the commits into logical sections to improve readability.

代码讲解:

    // kubecfg.go



ns = flag.String("ns", "", "If present, the namespace scope for this request.")
nsFile = flag.String("ns_file", os.Getenv("HOME")+"/.kubernetes_ns", "Path to the namespace file")


// Load namespace information for requests
// Check if the namespace was overriden by the -ns argument
ctx := api.NewDefaultContext()
if len(*ns) > 0 {
ctx = api.WithNamespace(ctx, *ns)
} else {
nsInfo, err := kubecfg.LoadNamespaceInfo(*nsFile)
if err != nil {
glog.Fatalf("Error loading current namespace: %v", err)
}
ctx = api.WithNamespace(ctx, nsInfo.Namespace)
}


r := c.Verb(verb).Namespace(api.Namespace(ctx)).Path(path)

kubecfg 可以传 ns 参数,ns 参数会被放入 ctx 中,并发送给 apiserver

// Context carries values across API boundaries.
type Context interface {
Value(key interface{}) interface{}
}

// The key type is unexported to prevent collisions
type key int

// namespaceKey is the context key for the request namespace.
const namespaceKey key = 0

// NewContext instantiates a base context object for request flows.
func NewContext() Context {
return context.TODO()
}

// NewDefaultContext instantiates a base context object for request flows in the default namespace
func NewDefaultContext() Context {
return WithNamespace(NewContext(), NamespaceDefault)
}

// WithValue returns a copy of parent in which the value associated with key is val.
func WithValue(parent Context, key interface{}, val interface{}) Context {
internalCtx, ok := parent.(context.Context)
if !ok {
panic(stderrs.New("Invalid context type"))
}
return context.WithValue(internalCtx, key, val)
}

// WithNamespace returns a copy of parent in which the namespace value is set
func WithNamespace(parent Context, namespace string) Context {
return WithValue(parent, namespaceKey, namespace)
}

// NamespaceFrom returns the value of the namespace key on the ctx
func NamespaceFrom(ctx Context) (string, bool) {
namespace, ok := ctx.Value(namespaceKey).(string)
return namespace, ok
}

在这段代码中,定义了一个常量 namespaceKey 作为 context 中 namespace 值的 key。

NewContext() 和 NewDefaultContext() 两个函数分别用于创建一个新的 context 对象,前者是一个空的 context,后者是一个默认设置了 namespace 的 context。

WithValue() 函数用于在给定 context 的基础上创建一个新的 context,并在新 context 中设置给定的键值对。对应地,WithNamespace() 则是用于在给定 context 的基础上创建一个新 context,并在新 context 中设置 namespace。

NamespaceFrom() 用于从 context 中获取 namespace,如果获取成功,返回 namespace 和 true,否则返回空字符串和 false。

//apiserver.go

func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w http.ResponseWriter, storage RESTStorage) {
ctx := api.NewContext()
sync := req.URL.Query().Get("sync") == "true"
timeout := parseTimeout(req.URL.Query().Get("timeout"))
// TODO for now, we pull namespace from query parameter, but according to spec, it must go in resource path in future PR
// if a namespace if specified, it's always used.
// for list/watch operations, a namespace is not required if omitted.
// for all other operations, if namespace is omitted, we will default to default namespace.
namespace := req.URL.Query().Get("namespace")
if len(namespace) > 0 {
ctx = api.WithNamespace(ctx, namespace)
}
switch req.Method {
case "GET":
switch len(parts) {
case 1:
label, err := labels.ParseSelector(req.URL.Query().Get("labels"))
if err != nil {
errorJSON(err, h.codec, w)
return
}
field, err := labels.ParseSelector(req.URL.Query().Get("fields"))
if err != nil {
errorJSON(err, h.codec, w)
return
}
list, err := storage.List(ctx, label, field)
if err != nil {
errorJSON(err, h.codec, w)
return
}
if err := h.setSelfLink(list, req); err != nil {
errorJSON(err, h.codec, w)
return
}
writeJSON(http.StatusOK, h.codec, list, w)
case 2:
item, err := storage.Get(api.WithNamespaceDefaultIfNone(ctx), parts[1])

该方法接受一个资源路径(parts),一个 HTTP 请求对象(req),一个 HTTP 响应写入器(w),和一个 REST 存储对象(storage)作为参数。

首先,它创建一个新的上下文对象(ctx)。接着,它从 HTTP 请求的 URL 查询参数中提取 "sync" 和 "timeout" 参数,并解析它们。"sync" 参数指示请求是否需要同步执行,"timeout" 参数为同步请求提供超时时间。

然后,该方法从查询参数中获取 "namespace",如果该参数存在并且长度大于 0,则在上下文对象中设置 namespace 并传入 storage.Get 方法中


//etcd.go
// MakeEtcdListKey constructs etcd paths to resource directories enforcing namespace rules
func MakeEtcdListKey(ctx api.Context, prefix string) string {
key := prefix
ns, ok := api.NamespaceFrom(ctx)
if ok && len(ns) > 0 {
key = key + "/" + ns
}
return key
}

// MakeEtcdItemKey constructs etcd paths to a resource relative to prefix enforcing namespace rules. If no namespace is on context, it errors.
func MakeEtcdItemKey(ctx api.Context, prefix string, id string) (string, error) {
key := MakeEtcdListKey(ctx, prefix)
ns, ok := api.NamespaceFrom(ctx)
if !ok || len(ns) == 0 {
return "", fmt.Errorf("Invalid request. Unable to address and item without a namespace on context")
}
if len(id) == 0 {
return "", fmt.Errorf("Invalid request. Id parameter required")
}
key = key + "/" + id
return key, nil
}

在存取 etcd 数据时,路径 key 加上 namespace