硬核乾貨丨藉助多容器Pod,輕鬆擴展K8S中的應用

Kubernetes提供了巨大的靈活性和運行各種應用的能力。如果你的應用是雲原生微服務或12要素(12-factor)應用,那麼在Kubernetes中運行它們有可能會相對簡單。

但是,運行那些沒有明確設計爲在容器化環境中運行的應用程序呢?Kubernetes也可以處理這些問題,但是設置起來可能會比較麻煩。

Kubernetes提供的最強大的工具之一是多容器pod(儘管多容器pod在各種情況下對雲原生應用也很有用)。爲什麼要在一個 pod 中運行多個容器?因爲多容器pod可以讓你在不改變其代碼的情況下更改應用程序的行爲。

這在各種情況下都很有用,特別是對於那些最初沒有被設計成在容器中運行的應用程序來說,這很方便。我們來看看一個例子。

確保HTTP服務的安全

Elasticsearch是在容器流行之前誕生的(當然現在在Kubernetes中運行也十分簡單),它可以看成在虛擬機中運行的傳統Java應用的替代。

我們將Elasticsearch作爲示例應用程序,然後使用多容器pods來增強它。

以下是十分基本的(非生產環境就緒)Elasticsearch Deployment和服務:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: elasticsearch
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: elasticsearch
  template:
    metadata:
      labels:
        app.kubernetes.io/name: elasticsearch
    spec:
      containers:
        - name: elasticsearch
          image: elasticsearch:7.9.3
          env:
            - name: discovery.type
              value: single-node
          ports:
            - name: http
              containerPort: 9200
---
apiVersion: v1
kind: Service
metadata:
  name: elasticsearch
spec:
  selector:
    app.kubernetes.io/name: elasticsearch
  ports:
    - port: 9200
      targetPort: 9200

discovery.type環境變量是讓它以單個副本運行的必要條件。

Elasticsearch默認通過HTTP端口9200進行監聽。你可以通過在集羣中運行另一個Pod並curlelasticsearch服務來確認pod工作。

kubectl run -it --rm --image=curlimages/curl curl \
  -- curl http://elasticsearch:9200
{
  "name" : "elasticsearch-77d857c8cf-mk2dv",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "z98oL-w-SLKJBhh5KVG4kg",
  "version" : {
    "number" : "7.9.3",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "c4138e51121ef06a6404866cddc601906fe5c868",
    "build_date" : "2020-10-16T10:36:16.141335Z",
    "build_snapshot" : false,
    "lucene_version" : "8.6.2",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

現在,假設你正在向零信任安全模式發展,你需要對網絡上的所有流量進行加密。如果應用程序沒有原生的TLS支持,你會如何去做?

近期版本的Elasticsearch支持TLS,但它在之前很長一段時間內是一個付費功能。

我們首先想到的可能是用nginx ingress做TLS終止,因爲ingress是集羣中路由外部流量的組件。但這並不能滿足要求,因爲ingress pod和Elasticsearch pod之間的流量可能會在未加密的情況下通過網絡。

在這裏插入圖片描述 外部流量被路由到Ingress,然後路由到Pod

圖片

如果你在Ingress終止TLS,剩下的流量將不會加密。

一個能滿足要求的解決方案是在pod上加一個nginx代理容器,通過TLS進行監聽。從用戶到Pod的一路流量都是加密的。

圖片

如果在pod中包含一個代理容器,你可以在Nginx pod中終止TLS。

圖片

當你比較當前的設置時,你可以注意到,在Elasticsearch容器之前,流量一直是加密的。

以下是部署的情況:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: elasticsearch
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: elasticsearch
  template:
    metadata:
      labels:
        app.kubernetes.io/name: elasticsearch
    spec:
      containers:
        - name: elasticsearch
          image: elasticsearch:7.9.3
          env:
            - name: discovery.type
              value: single-node
            - name: network.host
              value: 127.0.0.1
            - name: http.port
              value: '9201'
        - name: nginx-proxy
          image: nginx:1.19.5
          volumeMounts:
            - name: nginx-config
              mountPath: /etc/nginx/conf.d
              readOnly: true
            - name: certs
              mountPath: /certs
              readOnly: true
          ports:
            - name: https
              containerPort: 9200
      volumes:
        - name: nginx-config
          configMap:
            name: elasticsearch-nginx
        - name: certs
          secret:
            secretName: elasticsearch-tls
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: elasticsearch-nginx
data:
  elasticsearch.conf: |
    server {
        listen 9200 ssl;
        server_name elasticsearch;
        ssl_certificate /certs/tls.crt;
        ssl_certificate_key /certs/tls.key;

        location / {
            proxy_pass http://localhost:9201;
        }
    }

讓我們來解讀一下:

  • Elasticsearch在端口9201上監聽localhost,而不是默認的0.0.0.0:9200(那是network.host和http.port環境變量的作用)。
  • 新的nginx-proxy容器通過HTTPS在9200端口監聽,並在9201端口代理請求到Elasticsearch。(elasticsearch-tls secret包含TLS證書和密鑰,例如可以用cert-manager生成)。

所以來自pod外部的請求會通過HTTPS進入9200端口的Nginx,然後轉發到9201端口的Elasticsearch。

在這裏插入圖片描述

你可以通過在集羣內發出HTTPS請求來確認它是否可以正常工作。

kubectl run -it --rm --image=curlimages/curl curl \
  -- curl -k https://elasticsearch:9200
{
  "name" : "elasticsearch-5469857795-nddbn",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "XPW9Z8XGTxa7snoUYzeqgg",
  "version" : {
    "number" : "7.9.3",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "c4138e51121ef06a6404866cddc601906fe5c868",
    "build_date" : "2020-10-16T10:36:16.141335Z",
    "build_snapshot" : false,
    "lucene_version" : "8.6.2",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

對於自簽名的TLS證書,-k版本是必要的。在生產環境中,你需要使用可信的證書。

快速查看日誌,顯示該請求通過了Nginx代理:


kubectl logs elasticsearch-5469857795-nddbn nginx-proxy | grep curl 10.88.4.127 - - [26/Nov/2020:02:37:07 +0000] "GET / HTTP/1.1" 200 559 "-" "curl/7.73.0-DEV" "-"


你也可以檢查你是否無法通過未加密的連接連接到Elasticsearch:

kubectl run -it --rm --image=curlimages/curl curl \
  -- curl http://elasticsearch:9200
<html>
<head><title>400 The plain HTTP request was sent to HTTPS port</title></head>
<body>
<center><h1>400 Bad Request</h1></center>
<center>The plain HTTP request was sent to HTTPS port</center>
<hr><center>nginx/1.19.5</center>
</body>
</html>

你已經強制執行了TLS,而無需接觸Elasticsearch代碼或容器鏡像。

代理容器是一種常見的模式

在pod中添加代理容器的做法很常見,以至於它有一個名字:Ambassador模式。

這篇文章中的所有模式在谷歌的一篇優秀論文中都有詳細描述。公衆號後臺回覆【論文】,獲取論文下載地址。

添加基本的TLS支持只是一個開始。這裏有一些其他的事情你可以用Ambassador模式來做:

  • 如果你想讓集羣中的所有流量都用TLS證書加密,你可能會在集羣中的每個pod中安裝一個nginx(或其他)代理。你甚至可以更進一步,使用相互TLS來確保所有的請求都是經過認證以及加密的。(這是Istio和Linkerd等服務網格使用的主要方法)。
  • 你可以使用代理來確保集中的OAuth授權通過驗證jwts來認證所有請求。例如,gcp-iap-auth,它可以驗證請求是否被GCP Identity-Aware Proxy認證。
  • 你可以通過安全隧道連接到外部數據庫。這對於那些沒有內置TLS支持的數據庫來說尤其方便(比如舊版本的Redis)。

多容器pod的工作原理

我們先來了解Kubernetes上pod和容器之間的區別,以便更好地瞭解其底層是如何工作的。

一個傳統的容器(例如由docker run啓動的容器)提供了幾種形式的隔離:

  • 資源隔離(如,內存限制)
  • 進程隔離
  • Filesystem和掛載隔離
  • 網絡隔離

Docker還有其他一些設置,但這些是最主要的。

底層使用的工具是Linux命名空間和控制組(cgroups)。

控制組是一種用來限制資源的便捷方法,比如一個特定進程可以使用的CPU或內存。例如,你可以說你的進程應該只使用2GB的內存和4個CPU核心中的一個。

命名空間則負責隔離進程以及限制該進程能看到的東西。例如,進程只能看到與它直接相關的網絡數據包,它無法看到流經網絡適配器的所有網絡數據包。或者你可以隔離filesystem,讓進程相信它可以訪問所有的filesystem。

圖片

從內核5.6版本開始,有八種命名空間,掛載命名空間是其中之一

圖片

有了掛載命名空間,你可以讓進程認爲它可以訪問主機上的所有目錄,而事實上它並沒有

圖片

掛載命名空間被設計爲隔離資源——在本例中是filesystem。

圖片

每個進程都可以看到同一個filesystem,同時還可以與其他進程隔離

如果你需要複習一下cgroups和namespaces,這裏有一篇很好的博客文章,深入探討了一些技術細節:

https://jvns.ca/blog/2016/10/10/what-even-is-a-container/

在Kubernetes上,容器提供了所有形式的隔離,除了網絡隔離。網絡隔離發生在pod層面。換句話說,一個pod中的每個容器都會有自己的filesystem、進程表等,但它們都會共享同一個網絡命名空間。

讓我們來看看一個簡單pod容器,以更好地瞭解它是如何工作的。

apiVersion: v1
kind: Pod
metadata:
  name: podtest
spec:
  containers:
    - name: c1
      image: busybox
      command: ['sleep', '5000']
      volumeMounts:
        - name: shared
          mountPath: /shared
    - name: c2
      image: busybox
      command: ['sleep', '5000']
      volumeMounts:
        - name: shared
          mountPath: /shared
  volumes:
    - name: shared
      emptyDir: {}

我們將上面的代碼段拆解一下:

  • 有兩個容器,這兩個容器都會沉睡一段時間。
  • 有一個emptyDir卷,它本質上是一個臨時的本地卷,在pod的生命週期內持續存在。
  • emptyDir卷安裝在每個pod中的/shared目錄下。

你可以使用kubectl exec看到卷被掛載在第一個容器上:

kubectl exec -it podtest --container c1 -- sh

該命令將終端會話連接到podtest pod中的容器c1。

kubectl exec的--container選項通常縮寫爲-c。

mount | grep shared
/dev/vda1 on /shared type ext4 (rw,relatime)

如你所見,一個卷掛載在/shared上——這就是我們之前創建的shared卷。現在我們來創建一些文件:

echo "foo" > /tmp/foo
echo "bar" > /shared/bar

我們從第二個容器中檢查相同的文件。首先連接到它:

kubectl exec -it podtest --container c2 -- sh
cat /shared/bar
bar
cat /tmp/foo
cat: can't open '/tmp/foo': No such file or directory

如你所見,在shared目錄中創建的文件在兩個容器上都是可用的,但/tmp中的文件卻不可用。這是因爲除了卷之外,容器的filesysytem之間是完全隔離的。

現在我們來看看網絡和進程隔離。一個很好的方法是使用命令ip link來查看網絡是如何設置的,它可以顯示Linux系統的網絡設備。讓我們在第一個容器中執行這個命令:

kubectl exec -it podtest -c c1 -- ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
178: eth0@if179: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1450 qdisc noqueue
    link/ether 46:4c:58:6c:da:37 brd ff:ff:ff:ff:ff:ff

在另一個容器中執行同樣的命令:

kubectl exec -it podtest -c c2 -- ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
178: eth0@if179: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1450 qdisc noqueue
    link/ether 46:4c:58:6c:da:37 brd ff:ff:ff:ff:ff:ff

你可以看到兩個容器都有:

  • 相同的設備eth0
  • 相同MAC地址:46:4c:58:6c:da:37

因爲MAC地址應該是全局唯一的,因此相同的地址清楚地標明,這些Pod共享同一個設備。

現在讓我們來看看網絡共享的操作吧!我們先連接到第一個容器:

ubectl exec -it podtest -c c1 -- sh

藉助nc啓動一個簡單的網絡監聽器:

nc -lk -p 5000 127.0.0.1 -e 'date'

該命令在端口5000的localhost上啓動一個監聽器,並向任何連接的TCP客戶端輸入date命令。

那麼第二個容器可以連接到它嗎?

使用以下命令在第二個容器中打開終端:

kubectl exec -it podtest -c c2 -- sh

現在你可以驗證第二個容器可以連接到該網絡監聽器,但不能看到nc進程:

telnet localhost 5000
Connected to localhost
Sun Nov 29 00:57:37 UTC 2020
Connection closed by foreign host

ps aux
PID   USER     TIME  COMMAND
    1 root      0:00 sleep 5000
   73 root      0:00 sh
   81 root      0:00 ps aux

通過telnet連接,可以看到date的輸出,證明nc監聽器在工作,但是ps aux(顯示容器上的所有進程)根本沒有顯示nc。這是因爲pod內的容器有進程隔離,但沒有網絡隔離。這就解釋了Ambassador模式的工作原理:

  • 由於所有的容器都共享同一個網絡命名空間,所以一個容器可以監聽所有的連接——甚至是外部的連接。
  • 其餘的容器只接受來自localhost的連接——拒絕任何外部連接。

接收外部流量的容器就是Ambassador,因此該模式也被稱爲Ambassador模式。

圖片

不過有一點很關鍵,要記住:因爲網絡命名空間是共享的,所以一個pod中的多個容器不能在同一個端口監聽。

讓我們來看看多容器pod的一些其他用例。

使用標準接口暴露指標

假設你已經標準化地使用Prometheus來監控Kubernetes集羣中的所有服務,但你使用的一些應用程序並沒有原生導出Prometheus指標(如,Elasticsearch)。

你能在不改變你的應用程序代碼的情況下,將Prometheus指標添加到你的pod中嗎?事實上,你可以,使用Adapter模式。

對於Elasticsearch的例子,讓我們在pod中添加一個 "exporter"容器,以Prometheus格式暴露各種Elasticsearch指標。

這並不困難,因爲有一個Elasticsearch的開源exporter(你還需要將相關端口添加到服務中):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: elasticsearch
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: elasticsearch
  template:
    metadata:
      labels:
        app.kubernetes.io/name: elasticsearch
    spec:
      containers:
        - name: elasticsearch
          image: elasticsearch:7.9.3
          env:
            - name: discovery.type
              value: single-node
          ports:
            - name: http
              containerPort: 9200
        - name: prometheus-exporter
          image: justwatch/elasticsearch_exporter:1.1.0
          args:
            - '--es.uri=http://localhost:9200'
          ports:
            - name: http-prometheus
              containerPort: 9114
---
apiVersion: v1
kind: Service
metadata:
  name: elasticsearch
spec:
  selector:
    app.kubernetes.io/name: elasticsearch
  ports:
    - name: http
      port: 9200
      targetPort: http
    - name: http-prometheus
      port: 9114
      targetPort: http-prometheus

一旦應用了這個功能,你就可以在9114端口找到暴露的指標:

kubectl run -it --rm --image=curlimages/curl curl \
  -- curl -s elasticsearch:9114/metrics | head
# HELP elasticsearch_breakers_estimated_size_bytes Estimated size in bytes of breaker
# TYPE elasticsearch_breakers_estimated_size_bytes gauge
elasticsearch_breakers_estimated_size_bytes{breaker="accounting",name="elasticsearch-ss86j"} 0
elasticsearch_breakers_estimated_size_bytes{breaker="fielddata",name="elasticsearch-ss86j"} 0
elasticsearch_breakers_estimated_size_bytes{breaker="in_flight_requests",name="elasticsearch-ss86j"} 0
elasticsearch_breakers_estimated_size_bytes{breaker="model_inference",name="elasticsearch-ss86j"} 0
elasticsearch_breakers_estimated_size_bytes{breaker="parent",name="elasticsearch-ss86j"} 1.61106136e+08
elasticsearch_breakers_estimated_size_bytes{breaker="request",name="elasticsearch-ss86j"} 16440
# HELP elasticsearch_breakers_limit_size_bytes Limit size in bytes for breaker
# TYPE elasticsearch_breakers_limit_size_bytes gauge

再次,你已經能夠改變你的應用程序的行爲,而無需實際改變你的代碼或容器鏡像。你已經暴露了標準化的Prometheus指標,這些指標可以被集羣範圍內的工具(如Prometheus Operator使用),從而實現了應用程序和底層基礎設施之間的良好分離。

Tailing logs

接下來,我們來看看Sidecar模式,在這一模式下你可以將容器添加到Pod,該pod可以以某些方式增強應用程序。

Sidecar模式十分通用,可以應用到不同類型的用例中。我們接下來探索以下sidecar的經典用例:log tailing sidecar。

在容器化環境中,最佳實踐是始終將日誌記錄到標準輸出,這樣可以集中收集和彙總日誌。但許多舊的應用程序被設計成日誌輸出到文件,而改變這一方式並非易事。而添加一個log tailing sidecar意味着你不需要更改原有的方式也可以實現日誌的集中收集和彙總。

我們繼續以Elasticsearch爲例,這可能會有點彆扭,因爲Elasticsearch容器默認是將日誌記錄到標準輸出的(而且讓它記錄到文件也不是件容易的事)。

以下是部署情況:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: elasticsearch
  labels:
    app.kubernetes.io/name: elasticsearch
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: elasticsearch
  template:
    metadata:
      labels:
        app.kubernetes.io/name: elasticsearch
    spec:
      containers:
        - name: elasticsearch
          image: elasticsearch:7.9.3
          env:
            - name: discovery.type
              value: single-node
            - name: path.logs
              value: /var/log/elasticsearch
          volumeMounts:
            - name: logs
              mountPath: /var/log/elasticsearch
            - name: logging-config
              mountPath: /usr/share/elasticsearch/config/log4j2.properties
              subPath: log4j2.properties
              readOnly: true
          ports:
            - name: http
              containerPort: 9200
        - name: logs
          image: alpine:3.12
          command:
            - tail
            - -f
            - /logs/docker-cluster_server.json
          volumeMounts:
            - name: logs
              mountPath: /logs
              readOnly: true
      volumes:
        - name: logging-config
          configMap:
            name: elasticsearch-logging
        - name: logs
          emptyDir: {}

日誌配置文件是一個單獨的ConfigMap,因爲它太長了所以這裏沒有包括它。

兩個容器共享相同的volume,名爲logs。Elasticsearch容器將日誌寫入該卷,而日誌容器只是從相應的文件中讀取並輸出到標準輸出。你可以用kubectl logs指定相應的容器來檢索日誌流:

kubectl logs elasticsearch-6f88d74475-jxdhl logs | head
{
  "type": "server",
  "timestamp": "2020-11-29T23:01:42,849Z",
  "level": "INFO",
  "component": "o.e.n.Node",
  "cluster.name": "docker-cluster",
  "node.name": "elasticsearch-6f88d74475-jxdhl",
  "message": "version[7.9.3], pid[7], OS[Linux/5.4.0-52-generic/amd64], JVM"
}
{
  "type": "server",
  "timestamp": "2020-11-29T23:01:42,855Z",
  "level": "INFO",
  "component": "o.e.n.Node",
  "cluster.name": "docker-cluster",
  "node.name": "elasticsearch-6f88d74475-jxdhl",
  "message": "JVM home [/usr/share/elasticsearch/jdk]"
}
{
  "type": "server",
  "timestamp": "2020-11-29T23:01:42,856Z",
  "level": "INFO",
  "component": "o.e.n.Node",
  "cluster.name": "docker-cluster",
  "node.name": "elasticsearch-6f88d74475-jxdhl",
  "message": "JVM arguments […]"
}

使用sidecar的好處是,流式傳輸到標準輸出並不是唯一的選擇。

如果你需要切換到一個自定義的日誌聚合服務,你可以只改變sidecar容器,而無需改變你的應用程序中任何其他東西。

其他sidecar用例

Sidecar有許多用例,日誌容器只是其中一個比較簡單的用例。

以下是你在其他方面可能用到的一些其他用例:

  • 實時重新加載ConfigMaps,而不需要重新啓動pod
  • 將 Hashicorp Vault 中的secret注入到應用程序中
  • 將本地 Redis 實例添加到你的應用程序中,以實現低延遲的內存緩存

準備運行pod

到目前爲止,本篇文章所介紹的所有多容器pod的例子都涉及到多個容器同時運行。Kubernetes還提供了運行Init Containers的能力,Init Containers是在 "常規 "容器啓動之前運行完成的容器。

這允許你在你的pod正式啓動之前運行一個初始化腳本。爲什麼你希望你的準備工作在一個單獨的容器中運行,而不是在你的容器的entrypoint腳本中添加一些初始化?

讓我們來看看Elasticsearch的一個實際例子。Elasticsearch文檔推薦在生產就緒部署中設置vm.max_map_count的sysctl設置。這在容器化環境中是有問題的,因爲沒有容器級的sysctl隔離,任何更改都必須發生在節點級。

在不能自定義Kubernetes節點的情況下,如何處理這個問題?

一種方法是在特權容器中運行Elasticsearch,這將使Elasticsearch能夠改變其主機節點上的系統設置,並改變entrypoint腳本以添加sysctls。但從安全角度來看,這將是非常危險的!如果Elasticsearch服務被入侵,攻擊者將擁有對其主機節點的root權限。你可以使用init container來一定程度上降低這個風險:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: elasticsearch
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: elasticsearch
  template:
    metadata:
      labels:
        app.kubernetes.io/name: elasticsearch
    spec:
      initContainers:
        - name: update-sysctl
          image: alpine:3.12
          command: ['/bin/sh']
          args:
            - -c
            - |
              sysctl -w vm.max_map_count=262144
          securityContext:
            privileged: true
      containers:
        - name: elasticsearch
          image: elasticsearch:7.9.3
          env:
            - name: discovery.type
              value: single-node
          ports:
            - name: http
              containerPort: 9200

pod在特權init container中設置了sysctl,之後Elasticsearch容器按預期啓動。

你仍然在使用一個特權容器,這並不是理想狀態,但至少它持續時間很短,所以攻擊面要低得多。

這是Elastic Cloud Operator推薦的方法: https://www.elastic.co/guide/en/cloud-on-k8s/current/k8s-virtual-memory.html

使用特權init container爲運行pod的節點做準備是一種相當常見的模式。例如,Istio使用init container來設置每次pod運行時的iptables規則。

使用init container的另一個原因是以某種方式準備 pod 的filesystem。一個常見的用例是secrets管理。

其他的init container用例

如果你使用類似HashicCorp Vault這樣的工具來管理secrets,而不是Kubernetes secrets,你可以在一個init container中檢索secrets,並將它們持久化到一個共享的emptyDir卷。

如下所示:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  labels:
    app.kubernetes.io/name: myapp
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: myapp
  template:
    metadata:
      labels:
        app.kubernetes.io/name: myapp
    spec:
      initContainers:
        - name: get-secret
          image: vault
          volumeMounts:
            - name: secrets
              mountPath: /secrets
          command: ['/bin/sh']
          args:
            - -c
            - |
              vault read secret/my-secret > /secrets/my-secret
      containers:
        - name: myapp
          image: myapp
          volumeMounts:
            - name: secrets
              mountPath: /secrets
      volumes:
        - name: secrets
          emptyDir: {}

現在secret/my-secret secret將在myapp容器的filesystem中可用。

這就是Vault Agent Sidecar Injector等系統工作的基本思路。然而,它們在實踐中相當複雜(結合mutating webhooks、init container和sidecars來隱藏大部分的複雜性)。

此外,還有一些其他你可能想要使用init container的原因:

  • 你希望數據庫遷移腳本在你的應用程序之前運行(這通常可以在一個entrypoint腳本中完成,但有時使用專用容器更容易做到這一點)。
  • 你想從S3或GCS中檢索一個你的應用所依賴的大文件(爲此使用一個init container有助於避免應用容器的臃腫)。

總 結

這篇文章涵蓋了相當多的內容,所以這裏有一個表格,列出了一些多容器模式,以及你什麼時候可能要使用它們:

圖片

如果你想深入研究這個問題,請務必閱讀官方文檔和原始容器設計模式文件:

https://kubernetes.io/docs/concepts/workloads/pods/

https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/45406.pdf

在這裏插入圖片描述 原文鏈接: https://learnk8s.io/sidecar-containers-patterns

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章