1. 程式人生 > >Kubernetes ConfigMap熱更新測試

Kubernetes ConfigMap熱更新測試

ConfigMap熱更新測試

ConfigMap是用來儲存配置檔案的kubernetes資源物件,所有的配置內容都儲存在etcd中,下文主要是探究 ConfigMap 的建立和更新流程,以及對 ConfigMap 更新後容器內掛載的內容是否同步更新的測試。

測試示例

假設我們在 default namespace 下有一個名為 nginx-config 的 ConfigMap,可以使用 kubectl命令來獲取:

$ kubectl get configmap nginx-config
NAME           DATA      AGE
nginx-config   1         99d

獲取該ConfigMap的內容。

kubectl get configmap nginx-config -o yaml
apiVersion: v1
data:
  nginx.conf: |-
    worker_processes 1;

    events { worker_connections 1024; }

    http {
        sendfile on;

        server {
            listen 80;

            # a test endpoint that returns http 200s
            location / {
                proxy_pass http://httpstat.us/200;
                proxy_set_header  X-Real-IP  $remote_addr;
            }
        }

        server {

            listen 80;
            server_name api.hello.world;

            location / {
                proxy_pass http://l5d.default.svc.cluster.local;
                proxy_set_header Host $host;
                proxy_set_header Connection "";
                proxy_http_version 1.1;

                more_clear_input_headers 'l5d-ctx-*' 'l5d-dtab' 'l5d-sample';
            }
        }

        server {

            listen 80;
            server_name www.hello.world;

            location / {


                # allow 'employees' to perform dtab overrides
                if ($cookie_special_employee_cookie != "letmein") {
                  more_clear_input_headers 'l5d-ctx-*' 'l5d-dtab' 'l5d-sample';
                }

                # add a dtab override to get people to our beta, world-v2
                set $xheader "";

                if ($cookie_special_employee_cookie ~* "dogfood") {
                  set $xheader "/host/world => /srv/world-v2;";
                }

                proxy_set_header 'l5d-dtab' $xheader;


                proxy_pass http://l5d.default.svc.cluster.local;
                proxy_set_header Host $host;
                proxy_set_header Connection "";
                proxy_http_version 1.1;
            }
        }
    }
kind: ConfigMap
metadata:
  creationTimestamp: 2017-08-01T06:53:17Z
  name: nginx-config
  namespace: default
  resourceVersion: "14925806"
  selfLink: /api/v1/namespaces/default/configmaps/nginx-config
  uid: 18d70527-7686-11e7-bfbd-8af1e3a7c5bd

ConfigMap中的內容是儲存到etcd中的,然後查詢etcd:

ETCDCTL_API=3 etcdctl get /registry/configmaps/default/nginx-config
/registry/configmaps/default/nginx-config

注意使用 v3 版本的 etcdctl API,下面是輸出結果:

k8s

v1	ConfigMap�

T

nginx-configdefault"*$18d70527-7686-11e7-bfbd-8af1e3a7c5bd28B
                                                            �ʀ����xz�


nginx.conf�
           worker_processes 1;

events { worker_connections 1024; }

http {
    sendfile on;

    server {
        listen 80;

        # a test endpoint that returns http 200s
        location / {
            proxy_pass http://httpstat.us/200;
            proxy_set_header  X-Real-IP  $remote_addr;
        }
    }

    server {

        listen 80;
        server_name api.hello.world;

        location / {
            proxy_pass http://l5d.default.svc.cluster.local;
            proxy_set_header Host $host;
            proxy_set_header Connection "";
            proxy_http_version 1.1;

            more_clear_input_headers 'l5d-ctx-*' 'l5d-dtab' 'l5d-sample';
        }
    }

    server {

        listen 80;
        server_name www.hello.world;

        location / {


            # allow 'employees' to perform dtab overrides
            if ($cookie_special_employee_cookie != "letmein") {
              more_clear_input_headers 'l5d-ctx-*' 'l5d-dtab' 'l5d-sample';
            }

            # add a dtab override to get people to our beta, world-v2
            set $xheader "";

            if ($cookie_special_employee_cookie ~* "dogfood") {
              set $xheader "/host/world => /srv/world-v2;";
            }

            proxy_set_header 'l5d-dtab' $xheader;


            proxy_pass http://l5d.default.svc.cluster.local;
            proxy_set_header Host $host;
            proxy_set_header Connection "";
            proxy_http_version 1.1;
        }
    }
}"

輸出中在 nginx.conf 配置檔案的基礎中增加了檔案頭內容,是kubernetes增加的。

程式碼

ConfigMap 結構體的定義:

// ConfigMap holds configuration data for pods to consume.
type ConfigMap struct {
	metav1.TypeMeta `json:",inline"`
	// Standard object's metadata.
	// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata
	// +optional
	metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`

	// Data contains the configuration data.
	// Each key must be a valid DNS_SUBDOMAIN with an optional leading dot.
	// +optional
	Data map[string]string `json:"data,omitempty" protobuf:"bytes,2,rep,name=data"`
}

在 staging/src/k8s.io/client-go/kubernetes/typed/core/v1/configmap.go 中ConfigMap 的介面定義:

// ConfigMapInterface has methods to work with ConfigMap resources.
type ConfigMapInterface interface {
	Create(*v1.ConfigMap) (*v1.ConfigMap, error)
	Update(*v1.ConfigMap) (*v1.ConfigMap, error)
	Delete(name string, options *meta_v1.DeleteOptions) error
	DeleteCollection(options *meta_v1.DeleteOptions, listOptions meta_v1.ListOptions) error
	Get(name string, options meta_v1.GetOptions) (*v1.ConfigMap, error)
	List(opts meta_v1.ListOptions) (*v1.ConfigMapList, error)
	Watch(opts meta_v1.ListOptions) (watch.Interface, error)
	Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.ConfigMap, err error)
	ConfigMapExpansion
}

在 staging/src/k8s.io/client-go/kubernetes/typed/core/v1/configmap.go 中建立 ConfigMap 的方法如下:

// Create takes the representation of a configMap and creates it.  Returns the server's representation of the configMap, and an error, if there is any.
func (c *configMaps) Create(configMap *v1.ConfigMap) (result *v1.ConfigMap, err error) {
	result = &v1.ConfigMap{}
	err = c.client.Post().
		Namespace(c.ns).
		Resource("configmaps").
		Body(configMap).
		Do().
		Into(result)
	return
}

通過 RESTful 請求在 etcd 中儲存 ConfigMap 的配置,該方法中設定了資源物件的 namespace 和 HTTP 請求中的 body,執行後將請求結果儲存到 result 中返回給呼叫者。

注意 Body 的結構

// Body makes the request use obj as the body. Optional.
// If obj is a string, try to read a file of that name.
// If obj is a []byte, send it directly.
// If obj is an io.Reader, use it directly.
// If obj is a runtime.Object, marshal it correctly, and set Content-Type header.
// If obj is a runtime.Object and nil, do nothing.
// Otherwise, set an error.

建立 ConfigMap RESTful 請求中的的 Body 中包含 ObjectMeta 和 namespace。

HTTP 請求中的結構體:

// Request allows for building up a request to a server in a chained fashion.
// Any errors are stored until the end of your call, so you only have to
// check once.
type Request struct {
	// required
	client HTTPClient
	verb   string

	baseURL     *url.URL
	content     ContentConfig
	serializers Serializers

	// generic components accessible via method setters
	pathPrefix string
	subpath    string
	params     url.Values
	headers    http.Header

	// structural elements of the request that are part of the Kubernetes API conventions
	namespace    string
	namespaceSet bool
	resource     string
	resourceName string
	subresource  string
	timeout      time.Duration

	// output
	err  error
	body io.Reader

	// This is only used for per-request timeouts, deadlines, and cancellations.
	ctx context.Context

	backoffMgr BackoffManager
	throttle   flowcontrol.RateLimiter
}

測試

分別測試使用 ConfigMap 掛載 Env 和 Volume 的情況。

更新使用ConfigMap掛載的Env

使用下面的配置建立 nginx 容器測試更新 ConfigMap 後容器內的環境變數是否也跟著更新。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: my-nginx
spec:
  replicas: 1
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: sz-pg-oam-docker-hub-001.tendcloud.com/library/nginx:1.9
        ports:
        - containerPort: 80
        envFrom:
        - configMapRef:
            name: env-config
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: env-config
  namespace: default
data:
  log_level: INFO

獲取環境變數的值

$ kubectl exec `kubectl get pods -l run=my-nginx  -o=name|cut -d "/" -f2` env|grep log_level
log_level=INFO

修改 ConfigMap

$ kubectl edit configmap env-config

修改 log_level 的值為 DEBUG。

再次檢視環境變數的值。

$ kubectl exec `kubectl get pods -l run=my-nginx  -o=name|cut -d "/" -f2` env|grep log_level
log_level=INFO

實踐證明修改 ConfigMap 無法更新容器中已注入的環境變數資訊。

更新使用ConfigMap掛載的Volume

使用下面的配置建立 nginx 容器測試更新 ConfigMap 後容器內掛載的檔案是否也跟著更新。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: my-nginx
spec:
  replicas: 1
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: sz-pg-oam-docker-hub-001.tendcloud.com/library/nginx:1.9
        ports:
        - containerPort: 80
      volumeMounts:
      - name: config-volume
        mountPath: /etc/config
      volumes:
        - name: config-volume
          configMap:
            name: special-config
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: special-config
  namespace: default
data:
  log_level: INFO
$ kubectl exec `kubectl get pods -l run=my-nginx  -o=name|cut -d "/" -f2` cat /tmp/log_level
INFO

修改 ConfigMap

$ kubectl edit configmap special-config

修改 log_level 的值為 DEBUG。

等待大概10秒鐘時間,再次檢視環境變數的值。

$ kubectl exec `kubectl get pods -l run=my-nginx  -o=name|cut -d "/" -f2` cat /tmp/log_level
DEBUG

我們可以看到使用 ConfigMap 方式掛載的 Volume 的檔案中的內容已經變成了 DEBUG。

總結

更新 ConfigMap 後:

  • 使用該 ConfigMap 掛載的 Env 不會同步更新
  • 使用該 ConfigMap 掛載的 Volume 中的資料需要一段時間(實測大概10秒)才能同步更新

ENV 是在容器啟動的時候注入的,啟動之後 kubernetes 就不會再改變環境變數的值,且同一個 namespace 中的 pod 的環境變數是不斷累加的,參考 Kubernetes中的服務發現與docker容器間的環境變數傳遞原始碼探究。為了更新容器中使用 ConfigMap 掛載的配置,可以通過滾動更新 pod 的方式來強制重新掛載 ConfigMap,也可以在更新了 ConfigMap 後,先將副本數設定為 0,然後再擴容。

參考