如何在不重建鏡像狀況下優雅的修改容器內容

如今咱們使用容器很是頻繁,偶爾有一些需求須要更改容器鏡像中的一些行爲,也許是一個很小的變化,通常咱們能想到的就是從新構建鏡像,可是這個咱們就須要從新構建發佈鏡像了,除了構建鏡像這種方式以外其實還有其餘方式能夠來實現這個需求。html

初始化容器

Init Containers 是爲了給 Pod 中定義的主容器提供附加功能的。它們在主容器以前執行,可使用不一樣的容器鏡像,若是出現任何故障,它們將阻止主容器的啓動,全部的日誌均可以很容易查看到,故障排除也至關簡單,它們就像在 Pod 中定義的任何其餘容器同樣。這種方法在數據庫等服務中比較經常使用,能夠根據配置參數對它們進行初始化和配置。nginx

下面的例子使用一個 emptyDir 來存儲由初始化容器初始化的數據。在這個示例,它只是一個簡單的 echo 命令,在實際的生產環境中,多是一個腳本,作一些更復雜的事情。git

apiVersion: apps/v1
kindDeployment
metadata:
  labels:
    app: nginx
  namenginx-init
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      initContainers:
        - name: prepare-webpage
          imagebusybox:1.28
          command: ["sh""-c"]
          args: [
              "set -x;               echo '<h2>Page prepared by an init container</h2>' > /web/index.html;               echo 'Init finished successfully'               ",
            ]
          volumeMounts:
            - mountPath: /web
              name: web
      containers:
        - imagenginx:1.19
          name: nginx
          volumeMounts:
            - mountPath: /usr/share/nginx/html/
              name: web
          ports:
            - containerPort80
              name: http
      volumes:
        - name: web
          emptyDir: {}

PostStart Hook

post-start hook 可用於在主容器啓動後執行一些操做,它能夠是在與容器相同的上下文中執行的腳本,也能夠是針對定義的端點執行的 HTTP 請求,可是,不能保證回調會在容器入口點(ENTRYPOINT)以前執行。在大多數狀況下,它多是一個 shell 腳本,Pod一直保持在ContainerCreating 狀態,直到這個腳本結束。因爲沒有可用的日誌,因此調試起來可能很棘手。這個方法最大的特色是,當主容器中的服務啓動時,腳本就會被執行,而且能夠用來與服務進行交互,經過適當的 readinessProbe 配置,這能夠提供一種很好的方式,在容許任何請求以前初始化應用程序。在下面的例子中,一個啓動後的鉤子會執行 echo 命令,但一樣這能夠是任何使用容器文件系統上可用的同一組文件來執行某種初始化的東西。github

apiVersion: apps/v1
kindDeployment
metadata:
  labels:
    app: nginx
  namenginx-hook
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - imagenginx:1.19
          name: nginx
          ports:
            - containerPort80
              name: http
          lifecycle:
            postStart:
              exec:
                command:
                  [
                    "sh",
                    "-c",
                    "sleep 5;set -x; echo '<h2>Page prepared by a PostStart hook</h2>' > /usr/share/nginx/html/index.html",
                  ]

Sidecar 容器

這種方法利用了 Pod 的概念 - 多個容器同時運行、共享 IPC 和網絡命名空間。在 Kubernetes 生態系統中,它已經被 Istio、Consul Connect 等項目普遍使用。這裏的假設是全部容器同時運行,這使得使用 sidecar 容器來修改主容器的行爲變得有點棘手。但這是可行的,它能夠用來與正在運行的應用程序或服務進行交互。我在 Jenkins Helm Chart 中使用了這個功能,其中有一個 sidecar 容器負責讀取 ConfigMap 對象和 Configuration-as-Code 配置項。web

在下面示例中一樣只是使用 echo 這個命令,不過須要注意的是,由於 sidecar 容器必須遵循 restartPolicy 設置,因此這個容器在完成動做後還必須處於運行狀態,示例中咱們使用的是一個簡單的 while 無限循環,在實際環境中,每每會是一個小的守護進程,像服務同樣一直運行。docker

apiVersion: apps/v1
kindDeployment
metadata:
  labels:
    app: nginx
  namenginx-sidecar
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - imagenginx:1.19
          name: nginx
          volumeMounts:
            - mountPath: /usr/share/nginx/html/
              name: web
          ports:
            - containerPort80
              name: http
        - name: prepare-webpage
          imagebusybox:1.28
          command: ["sh""-c"]
          args: [
              "set -x;               echo '<h2>Page prepared by a sidecar container</h2>' > /web/index.html;               while :;do sleep 9999;done               ",
            ]
          volumeMounts:
            - mountPath: /web
              name: web
      volumes:
        - name: web
          emptyDir: {}

EntryPoint

最後一種方法使用相同的容器鏡像,與 PostStart Hook 相似,只是它在主應用程序或服務以前運行。咱們在容器鏡像中都定義一個ENTRYPOINT 命令,咱們能夠利用它來執行一些腳本,這種方式常常被不少官方鏡像所使用,在這種方法中,咱們只須要預置本身的腳原本修改主容器的行爲。在實際生產環境中,其實咱們能夠提供一個修改後的原始入口點文件。shell

這個方法相對複雜一點,須要建立一個 ConfigMap,其中包含一個腳本內容,在主入口點以前執行。以下所示咱們修改 nginx 入口點的腳本,而後嵌入到下面的 ConfigMap 中。數據庫

apiVersionv1
kindConfigMap
metadata:
  namescripts
data:
  prestart-script.sh: |-
    #!/usr/bin/env bash
    echo '<h2>Page prepared by a script executed before entrypoint container</h2>' > /usr/share/nginx/html/index.html
    # 這是 "ENTRYPOINT CMD "從主容器鏡像定義中提取出來的
    exec /docker-entrypoint.sh nginx -g "daemon off;" 

有一點很是重要,就是最後一行與 exec,它執行的是原始的入口點腳本,必須與 Dockerfile 中定義的腳本徹底匹配,在這種狀況下,它須要額外的參數,這些參數是在 CMD 中定義的。如今讓咱們定義一下 Deployment 資源對象。api

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx-script
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - image: nginx:1.19
          name: nginx
          command: ["bash""-c""/scripts/prestart-script.sh"]
          ports:
            - containerPort: 80
              name: http
          volumeMounts:
            - mountPath: /scripts
              name: scripts
      volumes:
        - name: scripts
          configMap:
            name: scripts
            defaultMode: 0755 # <- 這個很重要

咱們用命令覆蓋入口點,咱們還必須確保咱們的腳本是以適當的權限掛載的(所以須要定義 defaultMode)。bash

總結

如今咱們來總結下上面幾種方式的差別。

容器講究的是可重用性,不少時候作一些小的調整,不須要從新構建整個容器的鏡像,這樣發佈和維護就會輕鬆不少。

來自 「開源世界 」 ,連接: http://ym.baisou.ltd/post/603.html ,如需轉載,請註明出處,不然將追究法律責任


 

相關文章
相關標籤/搜索