Z.S.K.'s Records

Gitlab学习(GitOps的最简实践)

任何能够被描述的内容都必须存储在Git库中 这个GitOps的理念, 今天来践行一下,不借助其它devops平台, 看看是不是能够实现工具闭环

Git是GitOps形成的最基础的内容,就像第一条原则“任何能够被描述的内容都必须存储在Git库中 ”描述的那样:通过使用Git作为存储声明性基础架构和应用程序代码的存储仓库,可以方便地监控集群,以及检查比较实际环境的状态与代码库上的状态是否一致。所以,我们的目标是描述系统相关的所有内容:策略,代码,配置,甚至监控事件和版本控制等,并且将这些内容全部存储在版本库中,在通过版本库中的内容构建系统的基础架构或者应用程序的时候,如果没有成功,则可以迅速的回滚,并且重新来过

当然如果团队内有自己的devops平台的话,那其实就没必要接着看了,这篇post主要给大家一种思路,如何借助已有的工具来实现端到端的CI/CD, 只适合于团队较小,没有多余的资源来开发自己的平台的场景.

开源工具

以下3个开源工具, 不在这里介绍, 大家可参考相应的连接

kustomize

kustomize是kubernetes官方推荐的声明式配置形式

https://izsk.me/2020/07/01/Kubernetes-kustomize/

sealed-secrets

sealed-secrets主要提供可以在git上以密文的形式存储敏感数据, 当发布到k8s集群时用私钥进行解密,解密之后就变成了正常的secret文件.

https://izsk.me/2020/06/24/Kubernetes-sealed-secrets/

configmapsecret

kubernetes官方是不支持在configmap中直接引用secret中的内容, 因此configmapsecret主要用于在配置中引用secret或者是configmap中的内容

https://izsk.me/2020/06/28/Kubernetes-configmap-reference-var-from-secret/

Demo实践

这个demo用于实践以下功能:

  1. 使用配置端到端的、使用kubernetes推荐的声明式配置管理的方式(kustomize)
  2. 敏感的密码之类的数据在git源文件中进行脱敏处理(sealed-secrets)
  3. 结合gitlab的ci/cd方式完成多环境部署

到这一步默认在集群中已安装了以上的开源库.

目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
app-kustomize
# ... 这里省略其它的代码文件
deploy
base
config.yaml
deployment.yaml
service.yaml
kustomization.yaml
overlay
1box
patch-deployment.yaml
kustomization.yaml
prod
patch-deployment.yaml
kustomization.yaml

以上文件的具体内容大家可参考这篇post

数据加密

业务中敏感的数据其实不会很多,大多都是k-v的形式,比如mysql的密码,token等,既然要在git上存储这些数据,那自然是要进行脱敏才放心, 以最小的知情范围存在

可以把敏感的数据都放在一个配置文件中,类似下面的形式,怎么简单怎么来

cat senserealty-secret-data.yaml

1
2
3
4
mongodb_password: mysql1234
redis_password: abcdefg
redis_port: 6379
...

然后通过sealed-secrets进行加密,发布到集群中, 如何操作, 请参考上面的链接.

发布到集群后会发现已经生成了一个secret文件, senserealty-secret-data用于存储敏感信息

那在配置文件中就可以通过configmapsecret直接引用这些数据了

Kustomize

通过使用kustomize来组织所有需要发布到集群中的文件, 是非常方便的, 特别是应对多套环境的时候,通过派生的形式来生成配置,大大减少维护成本.

如何使用请参考上面的链接

CI/CD

使用gitlab自带的CI/CD工具同样可以实现很不错的效果, 只需要为其搭建一个k8s集群做为Runner即可, 很大一部分都会围绕gitlab-ci.yaml文件展开, gitlab实现了自己的语法, 拓展性非常强, 参考

这里给一个参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
image: ubuntu:latest # Pipeline中各个步骤阶段的构建镜像没有指定时,默认使用这里指定的镜像

# 全局配置
workflow:
rules:
- if: $CI_MERGE_REQUEST_ID
when: never
- when: always

stages:
- makebinary
- buildpush
- updateconfig1box
- deploy1box

variables:
# 省略 ...

.k8supdateconfig: &k8supdateconfig
- "[ ! -f ${CONFIGPATH} ] && (echo 'config file NOT FOUND' && exit 1)"
- kubectl apply -f config.yaml -n ${KUBERNETES_NS}
- "kubectl patch deployment ${APP} -n ${KUBERNETES_NS} -p '{\"spec\": {\"template\": {\"metadata\": {\"annotations\": {\"com.xxx.configmap/hash\": \"'$(date +%s)'\"}}}}}'"

.k8srolloutupgrade: &k8srolloutupgrade
- kustomize edit set image a/b:c=${REGISTRY_ADDR}/${PUSH_REPO}/${BINARY_NAME}:${DEPLOY_VERSION}
- kustomize build . | kubectl apply -f - --record
# 省略 ...

对于只修改配置文件的场景, 由于业务上不想升级一个版本, 因此通过给pod打一个annotation来实现pod的重启.

验证

进入到容器的/etc/config/config.yml目录会看到生成了两个配置文件,且敏感信息已经被解密成明文.

其实还有一种情况: 业务中可能很多的应用都会连接mysql, 那么, 当需要修改mysql的账号时, 还需要一个一个地去只需要这些应用的参数然后重启, 一次还好, 如何碰到安全部门定时修改的要求时更苦逼, 这其实是可以优化的

通过上面的方式, 其它应用都引用的senserealty-secret-data这个secret里的内容, 那只需要修改这一个文件,重新发布一下即可

应用通过watch的方式来监听该文件的变动,一有变动自动重启即可, 这个其实也有开源工具: reloader,有兴趣的可以参考一下

这种方式本人已经在生产环境中得到了验证,非常实用.

当然上面的例子中只是使用了几种最常用的配置, 其实完全可以将其它的资源对象以声明式的形式保存在git中

就像GitOps推荐的一样:任何能够被描述的内容都必须存储在Git库中

目前有很多的开源库可以直接实现以上功能, 比如Flux,能够同步git的变更直接发布到集群中. 功能上更强大. 感兴趣的可以参考

问题

  1. kubectl apply -k 出现error: rawResources failed to read Resources
1
error: rawResources failed to read Resources: Load from path ../../base/ failed: '../../base/' must be a file (got d='/Users/zhoushuke/git_uni/gitlab.bj.sensetime.com/gitlabci-golang-demo/deploy/base')

出现这种情况是因为kustomizekubectl apply -k 版本不匹配, 可以使用kustomiz build . | kubectl apply -f -

另外一个情况下, bases已经在kustomize v2.1.0+ 版本中给去掉了,高版本的会直接将bases转换成resources,这个可以从渲染后的kustomization.yaml中看出

  1. patch时到底是覆盖还是新增

    cat base/deployment

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # ... 省略
    containers:
    name: p-expoter
    volumeMounts:
    - mountPath: "/etc/config/config.yml"
    name: demo-config
    readOnly: true
    subPath: config.yml
    volumes:
    - secret:
    items:
    - key: alertmanager.yaml
    path: config.yml
    secretName: p-expoter-senserealty-cms
    name: demo-config

    cat overlay/1box/patch-deploy.yaml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # ... 省略
    containers:
    name: p-expoter
    volumeMounts:
    - mountPath: "/etc/config/grafana.yml"
    name: demo-config
    readOnly: true
    subPath: grafana.yml
    volumes:
    - secret:
    items:
    - key: grafana.yaml
    path: grafana.yml
    secretName: p-expoter-senserealty-cms
    name: demo-config

    最终build的结果为

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    # ... 省略
    containers:
    name: p-expoter
    volumeMounts:
    - mountPath: /etc/config/grafana.yml
    name: demo-config
    readOnly: true
    subPath: grafana.yml
    - mountPath: /etc/config/config.yml
    name: demo-config
    readOnly: true
    subPath: config.yml
    volumes:
    - name: demo-config
    secret:
    items:
    - key: grafana.yaml
    path: grafana.yml
    secretName: p-expoter-senserealty-cms

    可以看到 mountPath是合并了, 而volumes则是覆盖了. 这里可以使用在kustomization.yaml中使用patch或者patchJson6902, 参考这里,如果无法确认是合并还是覆盖, 多多使用kustomize build命令

    当然这里只是举个例子, 如果想实现叠加的话, 完全可以把base/deployment.yaml下的volumes复制一份过来, 不用把结构搞的太复杂

参考文章:

转载请注明出处https://izsk.me

Z.S.K. wechat
Scan Me To Read on Phone
I know you won't do this,but what if you did?