Carvel和BOSH
这篇文章是2022年TUNA-JP Advent Calendar活动的第24天入选作品,主要介绍了由VMware作为主要赞助商开发的一组开源软件,它是Cloud Native Computing Foundation下的一个Sandbox项目,名为Carvel。本文旨在探究这种软件群诞生的背景,并解释了与Kubernetes出现之前存在的开源软件项目Cloud Foundry BOSH的设计理念和开发成员之间的关系。大部分内容都是回顾过去的经历,没有太多新颖的内容。主要是一些老人们无聊的往事。但是,我会在最后写一点重要的事情,请急需的读者直接阅读该部分。
回顾四年前的圣诞节日历文章
我對Kubernetes還不太了解,且沒有任何轉職經驗。我正在撰寫一篇名為《BOSH和釋放工程師》的文章。
ひとつのYAMLで仮想マシンベースの様々な分散システムを定義づけることができる BOSH というものが、確固たる理念のもとで設計されており、Cloud Foundry という非常に複雑なアプリケーションプラットフォームの構築と運用を支えていることをご紹介しました。
その後時代は流れ、今では Kubernetes をコンテナプラットフォームとしてその上でのアプリケーションの構築と運用が当たり前となりました。YAMLによってコンテナプラットフォーム上のワークロードを定義づけることもかなり当たり前になり、さらに Terraform などの Infrastructure as Code を支えるツールもいよいよ普及してきていることから、もはやBOSHを推してく世の中でもない感じになっています。
最近では、YAML や HCL などを Git レポジトリに格納して source of truth とし、その変更を契機にアプリケーションやインフラを自動的に更新してゆく継続的デリバリー(CD)のプラクティスである GitOps を、運用の省力化やワークフローの高速化の文脈で実践している皆さんも多いのではないでしょうか。
Kubernetes 和发布工程
在《BOSH和发布工程》这篇文章中,我们传达了一个观点,即考虑现代化的发布工程时,因为BOSH可以很好地处理这些问题,所以你可以放心地使用BOSH!
では、今われわれがようやく使い慣れた Kubernetes は、BOSH によって実現されていたリリースエンジニアリングの各要素をどこまで満たしてくれているのでしょうか。
最初看起来,使用Kubernetes 可以满足很大部分需求。
-
- Kubernetes では、ひとつまたは複数のYAMLによってコンテナプラットフォーム上のワークロードを統合的に定義づけることができるので、Identifiability と Reproduciability を満足している!
オレたちには手塩にかけて書き上げたYAMLがある。これさえあれば、どんな Kubernetes クラスタでも、全く同じ構成でワークロードをデプロイできる!
kubectl apply コマンドには冪等性があり、YAMLをうまいこと整えておきさえすれば、このコマンドだけでワークロードの新規デプロイも更新もできるので、Consistency と Agility を満足している!
俺たちはもはや、kubectl create やら kubectl patch やら、そういう手続き的な運用をしていない。オレたちには手塩にかけて書き上げたYAMLがある。こいつを更新したら、必ず kubectl apply コマンドを使って反映させることで宣言的な運用を実現、冪等性もバッチリや!
这些是真的吗?
在 Kubernetes 中仍存在的发布工程问题
让我们考虑下面展示的两种情况。似乎仅仅使用Kubernetes并不能解决问题。
这个选项有点难以回溯或复现问题。
貴方のその手塩にかけたYAMLに、例えば以下のような記述はないでしょうか?
例1: Please turn off the lights before leaving the room.
image: hoge-container-registry.com/hoge-project/hoge-app:latest
Pod や Deployment などのYAMLで使われるコンテナイメージの参照(ほとんどは image: のハッシュ(or ディクショナリー)の値として書かれる)において、例1のような latest タグは、とりあえずコンテナレジストリに置いてある最新のイメージを使ってデプロイできればそれでいい、動くのが確認できたらそれでOKという、動作検証の場合などには、とても便利です。
しかし、本番環境やそれに近い環境(ステージング環境、QA環境など)ではよくある話なのですが、直近にビルドしたコンテナイメージにバグが見つかったので、その前に正常動作していたコンテナイメージに戻して修正デプロイする、いわゆる「切り戻し」操作が必要になったとき、 latest タグを使っていると、いきなり困ることになります。コンテナレジストリのUIを眺め、リリースの直前まで動いてたイメージはどれだったかな・・・あれ、前回のリリースからだいぶ経ってて、それ以降にコンテナイメージの再ビルド・再pushがたくさん行われてて、どれが正解だか分からないぞ・・・みたいな状況。あるある、と思われた方が結構おられるのではないでしょうか。
例2: The company plans to expand its operations into international markets in order to increase its customer base and boost sales.
image: nginx:1.23.3-alpine
由于最新的标签无法明确指定使用哪个版本的软件,因此在发布时我们采用了将版本号作为图像标签的做法。这样,在回滚时可以使用先前正常运行的相同容器镜像,从而安心地回滚。没问题,没问题…
很遗憾。这个世界并不那么甜蜜。
nginx:1.23.3-alpine这个容器镜像并不只包含与nginx 1.23.3相关的文件。它是基于alpine:3.17.0的基础镜像构建的,而alpine:3.17.0中还包含了一些共享库,如libc.musl和libssl等。如果这些共享库进行了错误修复或改进,alpine:3.17.0这个容器镜像将会重新构建并替换DockerHub上的旧镜像。同样,nginx:1.23.3-alpine也将自动重新构建并替换。
换句话说,即使是相同的标签,随着时间的推移,容器镜像的内容也可能会发生变化。在上述示例中,由于共享库的替换,nginx的行为也可能会发生变化。因此,即使想要回滚也可能会出现问题,无法成功还原或者无法再现bug的情况也是可能发生的。
如果是在自己团队开发的应用程序中,通过在构建和发布时为容器映像适当地添加标签,可以在回滚等情况下使用所需的容器映像,因此在这一点上应该不会有问题。然而,对于像nginx和各种DBMS(如MySQL和PostgreSQL)这样的与应用程序一起运行的容器,如果需要从外部容器注册表(如DockerHub)导入映像并部署,就无法自动为容器映像添加标签,这可能会引起困扰。
可能的对策
在参考容器镜像时,不使用诸如:latest :3.17.0 :1.23.3-alpine等标签,而是采用诸如index.docker.io/library/nginx@sha256:dd8a054d7ef030e94a6449783605d6c306c1f69c10c2fa06b66a030e0d1db793等摘要值的表示方法。
通过这种方式,可以确保对容器镜像中的所有目录树都具有唯一性,从而能够顺利实现恢复到所需时间点或重现问题。
image: index.docker.io/library/nginx@sha256:dd8a054d7ef030e94a6449783605d6c306c1f69c10c2fa06b66a030e0d1db793
ただ、YAMLに書かれている全てのコンテナイメージの参照を digest 記法とするのは、手動で行うのはとても面倒です。
多くのコンテナからなる複雑な構成の場合、それぞれのコンテナイメージの digest をいちいち調べるのか・・・そのへん、何とか自動的によしなにやってくれないものなのか。
在BOSH方面做了什么?
BOSH において、Kubernetes におけるコンテナイメージに当たるものは、強いて言えば BOSH stemcell と BOSH release との組み合わせ、ということになります。
BOSH stemcell は VMのベースイメージであり、ビルドされた順番に沿ってバージョン番号が振られています。
BOSH release は言わば Source Tarball の形式で格納されたソフトウェアパッケージの集合体であり、それぞれのソフトウェアパッケージとしてはソースコードのバージョン番号あるいは Git commit ID などによって識別される特定のバージョンのみが格納されています。
BOSH によってデプロイされたVMに着目すると、stemcell のバージョン と release に含まれる各ソフトウェアパッケージのバージョンがありますが、stemcell バージョンと release バージョンが明記されているひとつの BOSH deployment config からデプロイされた結果として、いずれのバージョンも必ず一意となっています。
すなわち、BOSH deployment config において BOSH stemcell と BOSH release の各バージョンを明示すれば、過去のいかなる構成も再現可能となるわけです。
その2: ConfigMap や Secret の更新が反映されない
ConfigMap や Secret としてワークロードの設定値や設定ファイルを定義しておき、コンテナには configMapKeyRef や secretKeyRef 経由で環境変数を設定したり、ボリュームマウントして設定ファイルとしたりするようなことがよくありますね。
しかしほとんどの場合、ConfigMap や Secret のYAMLを更新して kubectl apply しても、その設定の変更が反映されるべき Pod が再作成されることもコンテナが再起動されることもなく、平然と変更前の設定のままで動き続けるだろうと思います。もし設定ファイルの変更を検知して勝手に設定をリロードするようなソフトウェアだったら、それはそれで便利かもしれませんが、コンテナじゃない環境で動かすような場合は逆に運用がシビアになりそうで、なかなかこわい実装だと思います。
例として、コンテナにボリュームマウントする nginx の設定ファイルを ConfigMap として設定し、これを kubectl apply で更新しても、その変更が効かない模様を、以下に示します。
$ cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-conf
labels:
app: nginx
data:
nginx.conf: |
worker_processes 1;
events {}
http {
access_log /mnt/access.log combined;
server {
listen 80 default_server;
root /usr/share/nginx/html;
index index.html;
}
}
---
apiVersion: v1
kind: Service
metadata:
name: nginx-lb
labels:
app: nginx
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.23.3-alpine
ports:
- containerPort: 80
volumeMounts:
- mountPath: /etc/nginx
readOnly: true
name: nginx-conf
volumes:
- name: nginx-conf
configMap:
name: nginx-conf
items:
- key: nginx.conf
path: nginx.conf
EOF
configmap/nginx-conf created
service/nginx-lb created
deployment.apps/nginx created
$ kubectl get all -l app=nginx
NAME READY STATUS RESTARTS AGE
pod/nginx-7d8ffdbdb9-j8vn5 1/1 Running 0 13s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/nginx-lb LoadBalancer 10.43.82.56 192.168.1.131 80:31716/TCP 13s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nginx 1/1 1 1 13s
NAME DESIRED CURRENT READY AGE
replicaset.apps/nginx-7d8ffdbdb9 1 1 1 13s
$ curl 192.168.1.131
【略】
$ k exec nginx-7d8ffdbdb9-j8vn5 -- cat /mnt/access.log
10.42.0.0 - - [24/Dec/2022:12:34:56 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.68.0"
$ cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-conf
labels:
app: nginx
data:
nginx.conf: |
worker_processes 1;
events {}
http {
log_format short "time: \$time_iso8601";
access_log /mnt/access.log short;
server {
listen 80 default_server;
root /usr/share/nginx/html;
index index.html;
}
}
EOF
configmap/nginx-conf configured
$ curl 192.168.1.131
【略】
$ k exec nginx-7d8ffdbdb9-j8vn5 -- cat /mnt/access.log
10.42.0.0 - - [24/Dec/2022:12:34:56 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.68.0"
10.42.0.0 - - [25/Dec/2022:12:35:02 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.68.0"
如果你不理解这个机制,那么这条日志看起来确实充满了不公平的感觉,但实际上问题的根源是nginx没有加载新的配置文件。
更新デプロイを完結させるためには、kubectl rollout restart deployment/??? するだの、 kubectl delete pod して Deployment から Pod が自動再作成されるのを待つだの、ちょっとした追加コマンドを実行しないといけないことになります。
CDとかがんばってやろうとしても、その点でハマったり、そこを解決するためにCDパイプラインに変な設定を追加しないといけなかったり、いっそやむなく諦めて手運用に戻ったりなんかしたりして。
可能采取的措施
每次更新 ConfigMap 或 Secret 时,会将其资源名称 (metadata.name) 更改为新名称,并相应地修改引用该 ConfigMap 或 Secret 的 Deployment 等配置中的名称。通过这样做,确保更新的 ConfigMap 或 Secret 也会对应更新的 Deployment 等相关配置,从而成功完成更新部署。
唔,太土气了。太过土气了… 那边的事情,能不能自动化地处理好呢?
BOSH都做了什么?
BOSH stemcell中的任务组(在BOSH中称为job)的配置值可以在部署配置或指导者运行配置中进行记录。
当更新这些配置值进行部署时,BOSH指导者会提取差异,确定哪些虚拟机需要进行更新,并将配置值传递给相应的虚拟机,重新启动所有的job(或根据需要重新创建虚拟机)。
无论是部署配置还是指导者运行配置,都具有易于确定哪些虚拟机是配置更新对象的模式,并且设计可以进行无缺漏和高效配置更改。
Carvel 能够满足 Kubernetes 上的发布工程需求。
2020年8月,Carvel官方博客上发布了一篇文章,提到了大胆秀出你的情感。
Carvel was born from frustration with existing tools
Kubernetes本身是一个不错的东西,但从发布工程的角度来看,还有一些小问题尚未解决,并且其他工具也各有长短。因此,我们只能权宜之计地开发了Carvel工具集。
使用 kbld 将所有在 YAML 中写入的容器镜像引用替换为摘要(digest)格式
Carvel のツール群のひとつに kbld というものがあります。
例えば、以下のように使えます。
$ cat <<EOF | kbld -f -
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:1.23.3-alpine
EOF
resolve | final: nginx:1.23.3-alpine -> index.docker.io/library/nginx@sha256:dd8a054d7ef030e94a6449783605d6c306c1f69c10c2fa06b66a030e0d1db793
---
apiVersion: v1
kind: Pod
metadata:
annotations:
kbld.k14s.io/images: |
- origins:
- resolved:
tag: 1.23.3-alpine
url: nginx:1.23.3-alpine
url: index.docker.io/library/nginx@sha256:dd8a054d7ef030e94a6449783605d6c306c1f69c10c2fa06b66a030e0d1db793
name: nginx
spec:
containers:
- image: index.docker.io/library/nginx@sha256:dd8a054d7ef030e94a6449783605d6c306c1f69c10c2fa06b66a030e0d1db793
name: nginx
Succeeded
如果将原始的YAML通过管道传递给kbld,则它会提取出image:并将其替换为digest表示法,并输出修改后的YAML。在进行GitOps时,为了解决回退和故障再现时的问题,应该在将YAML存储到Git仓库之前,通过kbld将所有容器镜像的引用替换为digest表示法。
确保不漏掉工作负载配置的更改并进行更新部署的kapp
在Carvel的工具集中,有一个叫做kapp的工具。
它是一个很方便的命令,可以在更新部署之前预览变更差异。可以使用”kapp deploy -a [app_name] -c -f”命令来代替”kubectl apply -f”命令。实际上,它还具有许多其他的小众功能之一就是Versioned Resources(版本化资源)。
在更新工作负载配置时,如果想要对 ConfigMap 和 Secret 等资源进行反映,请在这些资源上添加注释 kapp.k14s.io/versioned: “”,通过为原始YAML中所写资源名称添加版本号的别名来创建实际的资源,并自动将引用该资源的部署等对象的引用目标更改为相应的别名。
因为我认为你可能不太理解这是什么意思,所以我会在下面展示使用 kapp 可以成功进行 nginx 的 ConfigMap 配置更改,而不是使用 kubectl apply 无法成功的情况。
$ cat <<EOF | kapp deploy -a nginx -f - -y
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-conf
labels:
app: nginx
annotations:
kapp.k14s.io/versioned: ""
data:
nginx.conf: |
worker_processes 1;
events {}
http {
access_log /mnt/access.log combined;
server {
listen 80 default_server;
root /usr/share/nginx/html;
index index.html;
}
}
---
apiVersion: v1
kind: Service
metadata:
name: nginx-lb
labels:
app: nginx
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.23.3-alpine
ports:
- containerPort: 80
volumeMounts:
- mountPath: /etc/nginx
readOnly: true
name: nginx-conf
volumes:
- name: nginx-conf
configMap:
name: nginx-conf
items:
- key: nginx.conf
path: nginx.conf
EOF
Target cluster 'https://127.0.0.1:6443' (nodes: home03, 1+)
Changes
Namespace Name Kind Age Op Op st. Wait to Rs Ri
default nginx Deployment - create - reconcile - -
^ nginx-conf-ver-1 ConfigMap - create - reconcile - -
^ nginx-lb Service - create - reconcile - -
Op: 3 create, 0 delete, 0 update, 0 noop, 0 exists
Wait to: 3 reconcile, 0 delete, 0 noop
4:33:06PM: ---- applying 1 changes [0/3 done] ----
4:33:06PM: create configmap/nginx-conf-ver-1 (v1) namespace: default
4:33:06PM: ---- waiting on 1 changes [0/3 done] ----
4:33:06PM: ok: reconcile configmap/nginx-conf-ver-1 (v1) namespace: default
4:33:06PM: ---- applying 2 changes [1/3 done] ----
4:33:07PM: create service/nginx-lb (v1) namespace: default
4:33:07PM: create deployment/nginx (apps/v1) namespace: default
4:33:07PM: ---- waiting on 2 changes [1/3 done] ----
4:33:07PM: ok: reconcile service/nginx-lb (v1) namespace: default
4:33:07PM: ongoing: reconcile deployment/nginx (apps/v1) namespace: default
4:33:07PM: ^ Waiting for generation 2 to be observed
4:33:07PM: L ok: waiting on replicaset/nginx-859ffcd8c (apps/v1) namespace: default
4:33:07PM: L ok: waiting on pod/nginx-859ffcd8c-ft64b (v1) namespace: default
4:33:07PM: ---- waiting on 1 changes [2/3 done] ----
4:33:07PM: ok: reconcile deployment/nginx (apps/v1) namespace: default
4:33:07PM: ---- applying complete [3/3 done] ----
4:33:07PM: ---- waiting complete [3/3 done] ----
Succeeded
$ kubectl get all,cm -l app=nginx
NAME READY STATUS RESTARTS AGE
pod/nginx-5667ccd54b-ll67q 1/1 Running 0 5m9s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/nginx-lb LoadBalancer 10.43.154.165 192.168.1.131 80:31150/TCP 2m34s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nginx 1/1 1 1 2m34s
NAME DESIRED CURRENT READY AGE
replicaset.apps/nginx-5667ccd54b 1 1 1 5m9s
replicaset.apps/nginx-859ffcd8c 0 0 0 8m59s
NAME DATA AGE
configmap/nginx-conf-ver-1 1 2m34s
$ curl 192.168.1.131
【略】
$ k exec nginx-859ffcd8c-ft64b -- cat /mnt/access.log
10.42.0.0 - - [24/Dec/2022:12:56:02 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.68.0"
$ cat <<EOF | kapp deploy -a nginx -f - -y
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-conf
labels:
app: nginx
annotations:
kapp.k14s.io/versioned: ""
data:
nginx.conf: |
worker_processes 1;
events {}
http {
log_format short "time: \$time_iso8601";
access_log /mnt/access.log short;
server {
listen 80 default_server;
root /usr/share/nginx/html;
index index.html;
}
}
---
apiVersion: v1
kind: Service
metadata:
name: nginx-lb
labels:
app: nginx
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.23.3-alpine
ports:
- containerPort: 80
volumeMounts:
- mountPath: /etc/nginx
readOnly: true
name: nginx-conf
volumes:
- name: nginx-conf
configMap:
name: nginx-conf
items:
- key: nginx.conf
path: nginx.conf
EOF
Target cluster 'https://127.0.0.1:6443' (nodes: home03, 1+)
Changes
Namespace Name Kind Age Op Op st. Wait to Rs Ri
default nginx Deployment 3m update - reconcile ok -
^ nginx-conf-ver-2 ConfigMap - create - reconcile - -
Op: 1 create, 0 delete, 1 update, 0 noop, 0 exists
Wait to: 2 reconcile, 0 delete, 0 noop
4:36:56PM: ---- applying 1 changes [0/2 done] ----
4:36:56PM: create configmap/nginx-conf-ver-2 (v1) namespace: default
4:36:56PM: ---- waiting on 1 changes [0/2 done] ----
4:36:56PM: ok: reconcile configmap/nginx-conf-ver-2 (v1) namespace: default
4:36:56PM: ---- applying 1 changes [1/2 done] ----
4:36:57PM: update deployment/nginx (apps/v1) namespace: default
4:36:57PM: ---- waiting on 1 changes [1/2 done] ----
4:36:57PM: ongoing: reconcile deployment/nginx (apps/v1) namespace: default
4:36:57PM: ^ Waiting for generation 4 to be observed
4:36:57PM: L ok: waiting on replicaset/nginx-859ffcd8c (apps/v1) namespace: default
4:36:57PM: L ok: waiting on replicaset/nginx-5667ccd54b (apps/v1) namespace: default
4:36:57PM: L ok: waiting on pod/nginx-859ffcd8c-ft64b (v1) namespace: default
4:36:57PM: L ongoing: waiting on pod/nginx-5667ccd54b-ll67q (v1) namespace: default
4:36:57PM: ^ Pending: ContainerCreating
4:36:58PM: ok: reconcile deployment/nginx (apps/v1) namespace: default
4:36:58PM: ---- applying complete [2/2 done] ----
4:36:58PM: ---- waiting complete [2/2 done] ----
Succeeded
$ kubectl get all,cm -l app=nginx
NAME READY STATUS RESTARTS AGE
pod/nginx-5667ccd54b-ll67q 1/1 Running 0 5m9s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/nginx-lb LoadBalancer 10.43.154.165 192.168.1.131 80:31150/TCP 8m59s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nginx 1/1 1 1 8m59s
NAME DESIRED CURRENT READY AGE
replicaset.apps/nginx-5667ccd54b 1 1 1 5m9s
replicaset.apps/nginx-859ffcd8c 0 0 0 8m59s
NAME DATA AGE
configmap/nginx-conf-ver-1 1 8m59s
configmap/nginx-conf-ver-2 1 5m9s
$ curl 192.168.1.131
【略】
$ k exec nginx-5667ccd54b-ll67q -- cat /mnt/access.log
time: 2022-12-24T12:57:09+00:00
$ k describe deploy nginx
Name: nginx
Namespace: default
【略】
Volumes:
nginx-conf:
Type: ConfigMap (a volume populated by a ConfigMap)
Name: nginx-conf-ver-2
Optional: false
啊,这个是一个相当方便的东西啊。
尤其是在使用 GitOps 的情况下,特别是对于使用 kapp(或者更准确地说是 kapp-controller)的情况,一定要记得设置这个注解,非常重要。
卡维尔和BOSH有一定的关系。
Dmitriy Kalinin和John Ryan等人在Carvel项目中长期参与,实际上在加入Carvel之前就属于Pivotal,积极参与Cloud Foundry的贡献,而在Pivotal被VMware收购后,仍然继续在VMware Tanzu的产品开发中工作。除此之外,像Nima Kaviani等人虽未加入Pivotal / VMware,但也深度参与了Cloud Foundry的贡献,并与Carvel有紧密关联。
我认为,从Cloud Foundry BOSH到Kubernetes的转变过程中,释放工程的质量下降是可以理解的。多年来,Cloud Foundry BOSH所积累的崇高设计理念现在已被Carvel继承,将在Kubernetes生态系统中继续发挥作用。
最后一个,重要的事情
在使用GitOps的人群中,使用kapp或kapp-controller的人仍然属于少数派,我认为大多数人是通过使用ArgoCD、Flux、Spinnaker等工具来实现GitOps的。对于这样的人群,我强烈推荐一个工具。
在将YAML存储到Git仓库时,我们需要将所有容器镜像的引用替换为摘要表示法(格式为index.docker.io/library/nginx@sha256:dd8a054d7ef030e94a6449783605d6c306c1f69c10c2fa06b66a030e0d1db793)。