开源工具集Carvel在CI/CD流水线中的集成

k8s技术圈

共 15778字,需浏览 32分钟

 · 2022-02-27

CI/CD流水线中使用VMware开源工具集Carvel

Carvel项目简介

Carvel项目的主页: https://carvel.dev/

简介

  • 一套适用于开发者和平台运维者构建;分发;安装;管理K8s上容器化应用的工具集.
  • 遵循Unix哲学[1] Make each program do one thing well.

工具集中包含但不限于下图几款软件:

它们分别在应用发布的生命周期中发挥着不同的作用:    

    Carvel每个工具执行一项特定的功能, 这样使用者就可以决定在哪个阶段用哪个工具做哪个任务. 而不是做成一个单体式多功能的工具(如Helm). 这里对工具的使用并没有好坏之分, 只要适用于用户的环境和用户的DevSecOps文化的就是好的工具.

    下文中我会对Carvel中的ytt; kbld以及kapp做进一步的介绍, 分别展示其功能及特性. 最后在一个Gitlab CI中将这些工具串联起来, 打包一个Java Spring程序源码成容器镜像, 部署进Tanzu K8s集群.


YTT

    简介 这工具名字取得...emmmm, 我心想就工程师就这么直男吗, 八成是什么Yaml Template Tool啥的. 结果在一次Tanzu总工的视频上听到这ytt原意来自钇[2]元素(自以为不直男的一次知识点的炫技).

    官方文档对ytt[3]有详细的介绍, 我在这收敛一下重点, 然后再通过几个列子演示一下.

  • ytt识别近乎所有主流的Yaml配置(K8s Configuration, Concourse Pipeline, Docker Compose, GitHub Action workflow...)
  • Yaml进, Yaml出
  • 类Python语法

    一张ytt如何工作的示意图, 感觉不是很直观, 上用例.

    

    用例 我们用一个例子来展示ytt的多种功能, 内置变量, 外置变量, 简单数学公式, 覆盖变量. 首先我们制作一个K8s Deployment和Service的Yaml模版, 起名Deployment.yml:

#@ load("@ytt:data", "data")

#@ def labels():
app: "spring-demo"
team: "dev"
#@ end
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: spring-sample-demo
  labels: #@ labels()
spec:
  replicas: #@ data.values.replicas / 2
  selector:
    matchLabels: #@ data.values.labels1
  template:
    metadata:
      labels: #@ data.values.labels1
    spec:
      containers:
        - image: #@ data.values.image1
          name: spring-sample-app
          ports:
            - containerPort: #@ data.values.app_port
              name: http
---
apiVersion: v1
kind: Service
metadata:
  name: spring-sample-demo-svc
spec:
  selector:
    app: spring-demo
  type: NodePort
  ports:
  - port: #@ data.values.svc_port
    targetPort: #@ data.values.app_port
    nodePort: 30028

#@ load("@ytt:data", "data") 是告诉ytt加载外置变量来自values.yml.#@ def labels(): 是Yaml模版内变量, 定义了两个标签app: "spring-demo", team: "dev"

    在有效K8s字段中,我们让Deployment的标签来自内置变量labels(), 而ReplicaSet和Selector标签从外置变量values.yml中读取labels1. 副本数量replicas: #@ data.values.replicas / 2 做一个简单的除法. 其他的变量的Patch原则如出一辙.

    再看values.yml: 以#@data/values, 告诉ytt渲染成values. 类似Helm中的values, 作为运维者或者流水线制定者在该文件中通过注释让开发者填写与应用相关的参数. 真正部署到K8s里的配置文件通过ytt渲染生成.

#@data/values
---
replicas: 8 #需要部署几个实例
svc_port: 80 #集群内暴露的服务端口
app_port: 8080 #应用监听的端口
image1: "" #实例的容器镜像名字
# 一些需要的标签
labels1:
  app: "spring-demo"
  cluster: "tce-mc"

    这里我故意把values中image1的值设为空, 因为我希望通过覆盖方式填写进去, 比如这个镜像名字是来自CI流水线中的内置变量之类的.

执行 接下来我们在命令行中执行:

# 演示用途, 设置一个环境变量IMAGE1
export IMAGE1=docker.io/rock981119/spring-sample:ci-main

# 执行ytt
ytt -f Deployment.yml -f values.yml -v image1=$IMAGE1

    通过-v image1=$IMAGE1覆盖掉values.yml里的image1的值, 那么总体的输出结果为:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: spring-sample-demo
  labels:
    app: spring-demo
    team: dev
spec:
  replicas: 4
  selector:
    matchLabels:
      app: spring-demo
      cluster: tce-mc
  template:
    metadata:
      labels:
        app: spring-demo
        cluster: tce-mc
    spec:
      containers:
      - image: docker.io/rock981119/spring-sample:ci-main
        name: spring-sample-app
        ports:
        - containerPort: 8080
          name: http
---
apiVersion: v1
kind: Service
metadata:
  name: spring-sample-demo-svc
spec:
  selector:
    app: spring-demo
  type: NodePort
  ports:
  - port: 80
    targetPort: 8080
    nodePort: 30028

    这样不管是输出成文件还是通过管道符交给Kubectl apply -f-执行都是可以的. 通过这个例子我们简单小结一下, ytt懂得Yaml格式, 不同于Shell中使用sed或其他模版语言只能替换固定位置的变量. 语法也比较精简, 有Python基础即可.

    Python语法渲染这个点是ytt重点优点里我最喜欢的, 让我这种只会点Python的人很快就能用起来. 其次就是现在的工具实在是太多了, 考虑学习成本迫使我学习心态变得更功利. 例如之前学习的Terraform的时候, 我就很反感HashiCorp非要再搞出个HCL的语法. 学OPA的时候要学习Rego语法... 而这些语法除了这些独立的场景外别的地方都用不着. 官网有ytt对比其他模版工具的观点[4], 欢迎查阅.

    如果是自定义的K8s CRD, ytt也可以通过写Schema来渲染你要的Yaml层级, 篇幅问题就不演示更多的例子了, 更多更详细的说明在官网的文档中都有用例.


KBLD

简介kbld[5] (pronounced: kei·bild):

  • 镜像构建(委派 Docker, pack, kubectl-buildkit等工具)和推送;搬迁的编排工具
  • 解析Yaml中镜像的摘要, 渲染出新的Yaml将镜像的值替换成该镜像的摘要(而不是某个tag或latest), 确保调用的镜像是不可变的

    用例1 在Kubernetes安全原则中经常提及到: 永远不要使用image:latest. 其实就算你使用的是image:tag, 你也不能保证相同的tag的镜像已经被更改. 主流镜像仓库(如DockerHub; Harbor)支持引用镜像时采用哈希值摘要.

    

    即便镜像的便签不变, 只要构建时任何一层发生变化, 其哈希摘要都会发生变化. 刚才ytt渲染后的Yaml已经是我在Docker Hub上真实上传的镜像名+Tag. 我们看看ytt渲染后的Yaml再交给kbld渲染一次会如何. 执行: ytt -f Deployment.yml -f values.yml -v image1=$IMAGE1 | kbld -f -

resolve | final: docker.io/rock981119/spring-sample:ci-main -> index.docker.io/rock981119/spring-sample@sha256:e901302da31edf61cbaec68df230e8b0f5cc33932d43337f9dac8a548ff23b7e
---
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    kbld.k14s.io/images: |
      - origins:
        - resolved:
            tag: ci-main
            url: docker.io/rock981119/spring-sample:ci-main
        url: index.docker.io/rock981119/spring-sample@sha256:e901302da31edf61cbaec68df230e8b0f5cc33932d43337f9dac8a548ff23b7e
# 省略
    spec:
      containers:
      - image: index.docker.io/rock981119/spring-sample@sha256:e901302da31edf61cbaec68df230e8b0f5cc33932d43337f9dac8a548ff23b7e
#省略

---
apiVersion: v1
kind: Service
#省略

Succeeded

    kbld只能解析到镜像仓库可达并正确的镜像名, 比如你自己瞎写一个或者只是本地的镜像它则不能解析了. 除了解析镜像的哈希摘要外, kbld可以同时委派Docker或者Pack来构建镜像, 你也可以一步把构建的镜像再推到仓库里.

    用例2 构建镜像并推送至仓库, 文件名kbld.yml

---
apiVersion: kbld.k14s.io/v1alpha1
kind: Config
sources:
- image: docker.io/rock981119/spring-sample:ci-main
  path: apps/java-maven/
  pack:
    build:
      builder: paketobuildpacks/builder:tiny

---
apiVersion: kbld.k14s.io/v1alpha1
kind: Config
destinations:
- image: docker.io/rock981119/spring-sample:ci-main
  newImage: docker.io/rock981119/spring-sample
  tags: [latest, ci-main]

sources配置就是告诉kbld委派Pack构建本地镜像docker.io/rock981119/spring-sample:ci-main, path告诉Pack源码的位置, 你可以单独指定builder. 如果你的Pack设置了默认的builder也可以不指定. 关于kbld委派其他的构建工具可以参考官网文档[6].

destinations告诉kbld将哪个本地镜像重命名并推送至仓库, 推送时可以加若干标签, 他们的哈希摘要都是一致的.

    kbld不能直接对kbld.yml生效, 因为它的目的本身并不在于构建镜像和推送, 而是摄取正确的镜像哈希摘要填入到需要部署的Yaml中去.

    承接上一个ytt的用例, 我们继续联合两个工具一起工作, ytt渲染配置, kbld委派构建镜像并推送至仓库, 最终渲染出部署Yaml

ytt -f Deployment.yml -f values.yml -v image1=$IMAGE1 | kbld -f kbld.yml -f -

    最终输出的Yaml跟kbld的第一个用例是类似的, 但在你的Console中它已经完成了委派镜像构建和推送.


KAPP

    ytt和kbld的实质任务都是渲染出最终可用于部署的Yaml配置, 是时候该找个工具部署了. 事实上渲染后的Yaml文件可以直接用kubectl或者helm来部署了. 那我们再看看Carvel中的kapp又有什么不同.

这个官网[7]的一句话定义:

Deploy and view groups of Kubernetes resources as "applications". Apply changes safely and predictably, watching resources as they converge.

    简单来说当你部署一个Yaml Bundle的时候, 内容里包含了若干CRD, Deployment, Service, Ingress等, 其实它们是同一个应用. 通过kapp部署这个Yaml Bundle, kapp会将它实例化为一个app(自定义名字).

  • apply阶段识别Yaml中资源的部署先后顺序
  • apply阶段告知创建了何种配置
  • diff阶段以git格式告知产生了何种变化
  • 树状展示资源

    用例 我们用kapp将ytt和kbld渲染后的Yaml部署到已建好的Tanzu Kubernetes集群当中去:

ytt -f Deployment.yml -f values.yml -v image1=$IMAGE1 | kbld -f kbld.yml -f - | kapp deploy -a spring-demo -c -y -f -
#输出省略
@@ update deployment/spring-sample-demo (apps/v1) namespace: default @@
  ...
 16, 16               - ci-main
 17     -         url: index.docker.io/rock981119/spring-sample@sha256:feb86d80c50f95d269c6cb331ae3a36f6e3585b04ded0ff9aff1ad65edc974f7
     17 +         url: index.docker.io/rock981119/spring-sample@sha256:f8fc4021d58af607f5ef15a31091cdc759a2b958981a8fe9792654376e3e39c8
 18, 18     creationTimestamp: "2022-01-29T06:28:57Z"
 19, 19     generation: 4
  ...
138,138         containers:
139     -       - image: index.docker.io/rock981119/spring-sample@sha256:feb86d80c50f95d269c6cb331ae3a36f6e3585b04ded0ff9aff1ad65edc974f7
    139 +       - image: index.docker.io/rock981119/spring-sample@sha256:f8fc4021d58af607f5ef15a31091cdc759a2b958981a8fe9792654376e3e39c8
140,140           name: spring-sample-app
141,141           ports:

Changes

Namespace  Name                Kind        Conds.  Age  Op      Op st.  Wait to    Rs  Ri  
default    spring-sample-demo  Deployment  2/2 t   14m  update  -       reconcile  ok  -  

Op:      0 create, 0 delete, 1 update, 0 noop, 0 exists
Wait to: 1 reconcile, 0 delete, 0 noop
#输出省略

    因为演示, kapp部署时, -a为应用实例起名spring-demo, 我apply了多次, 用例中kapp输出了与上次变更了的内容. kapp ls罗列当前通过kapp部署的实例. kapp inspect -a spring-demo树状展示spring-demo实例的资源关系以及同步状态.

kapp inspect -a spring-demo
Target cluster 'https://192.168.31.100:6443' (nodes: tce-management-control-plane-86792, 1+)
#省略部分告警
Resources in app 'spring-demo'

Namespace  Name                                 Kind           Owner    Conds.  Rs  Ri  Age  
default    spring-sample-demo                   Deployment     kapp     2/2 t   ok  -   19m  
^          spring-sample-demo-65b8d65957        ReplicaSet     cluster  -       ok  -   4m  
^          spring-sample-demo-65b8d65957-brmpg  Pod            cluster  4/4 t   ok  -   4m  
^          spring-sample-demo-65b8d65957-jx2gw  Pod            cluster  4/4 t   ok  -   4m  
^          spring-sample-demo-65b8d65957-ls2nt  Pod            cluster  4/4 t   ok  -   4m  
^          spring-sample-demo-65b8d65957-vzn6b  Pod            cluster  4/4 t   ok  -   4m  
^          spring-sample-demo-667859679         ReplicaSet     cluster  -       ok  -   19m  
^          spring-sample-demo-6774567466        ReplicaSet     cluster  -       ok  -   15m  
^          spring-sample-demo-svc               Endpoints      cluster  -       ok  -   19m  
^          spring-sample-demo-svc               Service        kapp     -       ok  -   19m  
^          spring-sample-demo-svc-ncrlx         EndpointSlice  cluster  -       ok  -   19m  

Rs: Reconcile state
Ri: Reconcile information

11 resources

    kapp删除实例, 在Op列中可以看到, 实际删除的就是Deployment和Service.

kapp delete -a spring-demo
Changes

Namespace  Name                                 Kind           Conds.  Age  Op      Op st.  Wait to  Rs  Ri  
default    spring-sample-demo                   Deployment     2/2 t   21m  delete  -       delete   ok  -  
^          spring-sample-demo-65b8d65957        ReplicaSet     -       7m   -       -       delete   ok  -  
^          spring-sample-demo-65b8d65957-brmpg  Pod            4/4 t   7m   -       -       delete   ok  -  
^          spring-sample-demo-65b8d65957-jx2gw  Pod            4/4 t   7m   -       -       delete   ok  -  
^          spring-sample-demo-65b8d65957-ls2nt  Pod            4/4 t   7m   -       -       delete   ok  -  
^          spring-sample-demo-65b8d65957-vzn6b  Pod            4/4 t   7m   -       -       delete   ok  -  
^          spring-sample-demo-667859679         ReplicaSet     -       21m  -       -       delete   ok  -  
^          spring-sample-demo-6774567466        ReplicaSet     -       17m  -       -       delete   ok  -  
^          spring-sample-demo-svc               Endpoints      -       21m  -       -       delete   ok  -  
^          spring-sample-demo-svc               Service        -       21m  delete  -       delete   ok  -  
^          spring-sample-demo-svc-ncrlx         EndpointSlice  -       21m  -       -       delete   ok  -  

Op:      0 create, 2 delete, 0 update, 9 noop, 0 exists
Wait to: 0 reconcile, 11 delete, 0 noop



Carvel in CI/CD

    简介完这三个Carvel的工具后, 我们基本就可以用它们完成一个源码到镜像, 生成配置再到实例化部署的一个过程了. 那么我们把这个过程在CI/CD流水线中实践一下.

源码用的是Buildpack官方的例子Buildpack官方的例子源码

  1. 上传本地Gitlab Server. 如果你想安装Gitlab CE私有环境可以考虑我司在Bitnami上打包好的虚拟机镜像Bitnami Gitlab CE OVA[8], 还附有设置文档.
  2. 制作Deplpoyment.yml模版, values.yml, kbld.yml
  3. 为该项目注册一个Runner, 为了方便选用了Photon Linux, Shell执行模式
  4. 构建Gitlab Pipeline文件
  5. 验证  
  6. 拓扑与流程如上图, 下方是我的Gitlab Pipeline配置文件, .gitlab-ci.yml:
# This file is a template, and might need editing before it works on your project.
# To contribute improvements to CI/CD templates, please follow the Development guide at:
# https://docs.gitlab.com/ee/development/cicd/templates.html
# This specific template is located at:
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Getting-Started.gitlab-ci.yml

# This is a sample GitLab CI/CD configuration file that should run without any modifications.
# It demonstrates a basic 3 stage CI/CD pipeline. Instead of real tests or scripts,
# it uses echo commands to simulate the pipeline execution.
#
# A pipeline is composed of independent jobs that run scripts, grouped into stages.
# Stages run in sequential order, but jobs within stages run in parallel.
#
# For more information, see: https://docs.gitlab.com/ee/ci/yaml/index.html#stages

stages:          # List of stages for jobs, and their order of execution
  - test
  - build
  - deploy

test:
  stage: test
  tags:
    - linux
  script:
    - echo "测了, 但没完全测"

build:
  stage: build
  needs: ["test"]
  variables:
    IMAGE1: "docker.io/rock981119/spring-sample:ci-main"
  tags:
    - linux
  before_script:
    - echo "Before Script For Docker Hub Login"
    - echo $DOCKER_PWD | docker login --username $DOCKER_USER --password-stdin
  script:
    - echo " ----> 使用ytt和kbld渲染Yaml配置"
    - echo " kbld委派Pack构建镜像并推送至仓库"
    - ytt -f Deployment.yml -f values.yml -v image1=$IMAGE1 | kbld -f kbld.yml -f - >> app.yml
  artifacts:
    paths:
      - app.yml

deploy2tanzu:
  stage: deploy
  needs: ["build"]
  tags:
    - linux
  when: manual
  script:
    - echo "使用kapp部署应用实例"
    - kapp deploy -a $CI_PROJECT_NAME -c -y -f app.yml

    查看一下结果吧

ci-with-carvel [main] kapp ls
Target cluster 'https://192.168.31.100:6443' (nodes: tce-management-control-plane-86792, 1+)

Apps in namespace 'default'

Name            Namespaces  Lcs   Lca  
ci-with-carvel  default     true  1m  
tce-repo-ctrl   default     true  1m  

Lcs: Last Change Successful
Lca: Last Change Age

2 apps

Succeeded
ci-with-carvel [main] kapp inspect -a ci-with-carvel

Resources in app 'ci-with-carvel'

Namespace  Name                                 Kind           Owner    Conds.  Rs  Ri  Age  
default    spring-sample-demo                   Deployment     kapp     2/2 t   ok  -   34m  
^          spring-sample-demo-6798f54d9c        ReplicaSet     cluster  -       ok  -   2m  
^          spring-sample-demo-6798f54d9c-grm5k  Pod            cluster  4/4 t   ok  -   1m  
^          spring-sample-demo-6798f54d9c-q88tn  Pod            cluster  4/4 t   ok  -   1m  
^          spring-sample-demo-6798f54d9c-rxr5w  Pod            cluster  4/4 t   ok  -   2m  
^          spring-sample-demo-6798f54d9c-smf6j  Pod            cluster  4/4 t   ok  -   2m  
^          spring-sample-demo-746b764476        ReplicaSet     cluster  -       ok  -   34m  
^          spring-sample-demo-svc               Endpoints      cluster  -       ok  -   34m  
^          spring-sample-demo-svc               Service        kapp     -       ok  -   34m  
^          spring-sample-demo-svc-8md44         EndpointSlice  cluster  -       ok  -   34m  

Rs: Reconcile state
Ri: Reconcile information

10 resources

kubectl get all
NAME                                      READY   STATUS    RESTARTS   AGE
pod/spring-sample-demo-6798f54d9c-grm5k   1/1     Running   0          2m26s
pod/spring-sample-demo-6798f54d9c-q88tn   1/1     Running   0          2m28s
pod/spring-sample-demo-6798f54d9c-rxr5w   1/1     Running   0          2m42s
pod/spring-sample-demo-6798f54d9c-smf6j   1/1     Running   0          2m43s

NAME                             TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
service/kubernetes               ClusterIP   100.64.0.1               443/TCP        4d2h
service/spring-sample-demo-svc   NodePort    100.65.232.184           80:30028/TCP   35m

NAME                                 READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/spring-sample-demo   4/4     4            4           35m

NAME                                            DESIRED   CURRENT   READY   AGE
replicaset.apps/spring-sample-demo-6798f54d9c   4         4         4       2m43s
replicaset.apps/spring-sample-demo-746b764476   0         0         0       35m

目标达到了,看起来不错.

才疏学浅,只把玩了一下Carvel Toolset的小功能, 希望对大家DevSecOps的日常有帮助, 多多支持与关注我们VMware的开源项目.

结语

    结语就不上什么价值观了, 这一年比较懒, 没输出啥内容, 希望明年能更努力. 最后祝大家虎年行大运, 走出一个虎虎生风.


参考资料

[1]

Unix philosophy: https://en.wikipedia.org/wiki/Unix_philosophy

[2]

Yttrium: https://en.wikipedia.org/wiki/Yttrium

[3]

About ytt: https://carvel.dev/ytt/docs/latest/

[4]

ytt-vs-x: https://carvel.dev/ytt/docs/v0.38.0/ytt-vs-x/

[5]

kbld: https://carvel.dev/kbld/docs/latest/

[6]

kbld委派其他的构建工具: https://carvel.dev/kbld/docs/v0.32.0/config/

[7]

kapp: https://carvel.dev/kapp/

[8]

Bitnami Gitlab CE OVA: https://bitnami.com/stack/gitlab

浏览 134
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报