云原生环境下的DevOps新模式:GitOps与CI/CD相结合

结合 ArgoCD、Kustomize 和 GitLab CI 实现 DevOps

# GitOps概述

GitOps是一种实现 DevOps 的策略,使用 Git 作为事实的唯一来源,声明式的描述整个应用系统。GitOps会对比git仓库中的声明信息与k8集群中的实际运行状态之间的差异,然后根据情况进行自动更新或回滚。

GitOps 的几个特点:

  1. 版本控制:所有基础设施和应用程序的更改都在 Git 中进行,这为审查、跟踪和回滚更改提供了方便。这也提高了透明度,因为任何人都可以查看 Git 历史,看到何时、由谁以及为什么进行了更改。
  2. 声明性基础设施:所有系统组件在代码中定义并存储在版本控制系统中。这意味着可以轻松复制或恢复整个环境,极大地简化了故障恢复和灾难恢复。
  3. 自动化:GitOps 使用自动化 "推拉" 方法,确保集群状态始终与 Git 中定义的状态一致。这减少了人工干预的需求,减少了错误,提高了效率。
  4. 自修复:如果集群状态与 Git 中定义的状态不一致,GitOps 将自动检测并修复这种状态,保持系统稳定性和一致性。

总的来说,GitOps 是一种特定的 DevOps 和 CI/CD 实现方式,它侧重于使用 Git 作为管理基础设施和应用程序的声明性版本控制的事实来源,并强调自动化和自修复。

# ArgoCD

ArgoCD是一个用于k8s的声明式GitOps持续交付工具,它遵循gitops模式,支持多种k8s manifests 比如helm、kustomize。他可以在指定环境中自动部署应用程序状态。

Argo CD 通过kubernetes控制器实现,通过持续监控集群中正在运行的应用程序并将当前的状态信息与所需的目标状态(环境配置仓库)进行比较。通过Diff实时状态与目标状态,如果存在差异则应用进入OutOfSync状态;Argo CD会报告和可视化差异,同时提供工具自动或手动的方式将实时状态同步回所需目标状态

Argo CD在之前部署的kubesphere中是内置,在kubesphere中开启devops组件就可以使用了。

# Kustomize

kustomize是k8s中的一个管理配置工具,它支持对基础的k8s对象文件如Deployment、Pod、Service进行个性化定制,而无需使用模板。

关于kustomize的描述在这https://kubernetes.io/zh-cn/docs/tasks/manage-kubernetes-objects/kustomization/#kustomize-feature-list。

# GitOps的安全隐患与对策

由于GitOps的自动化和自修复的特点以及它依赖于git中声明文件的特性。导致变相的将产品发布流程权限下放给了git仓库中的developer。这意味着所有有权限访问仓库的人都可以修改配置。以下是这个问题的一些解决方案。

  1. 使用分支策略和保护分支:设置 Git 分支策略,只允许特定的人(例如,领导或者 DevOps 团队的成员)向生产环境的分支提交代码。其他开发者可以在开发或者测试分支中修改配置,但是他们不能直接修改生产环境的配置。

    我的实践是git中常存main与dev两个分支,main中存放当前生产环境代码与kustomization 的prod overlay。dev中存放开发环境代码与kustomization的dev overlay。

  2. 实施代码审查:即使开发者可以提交修改,也应该实施代码审查的策略,确保所有的修改都经过了审查。这样,即使有人试图修改生产环境的配置,也需要得到其他人的批准。

  3. 使用权限管理工具:许多 Git 仓库(例如 GitHub、GitLab 等)提供了详细的权限管理功能。你可以利用这些功能来限制谁可以修改特定文件或者目录。

  4. 将配置存储在单独的仓库:另一种可能的解决方案是将配置存储在单独的仓库中。这样,你可以更精确地控制谁可以访问和修改这个仓库。这也能帮助你将应用代码和配置分开,降低管理的复杂性。

  5. 使用自动化测试:自动化测试可以帮助你确保所有的修改都是安全的,并且不会对生产环境产生负面影响。你可以在 CI/CD 流程中添加自动化测试,确保所有的修改都通过了测试。

# Java项目demo

# 1. gilab-ci

# 1.1 k8s中部署Gitlab runner

# 1.1.1 Gitlab新建runner

Gitlab 版本为16.1

可以选择为项目创建独享runner或为群组创建共享runner。我的是一个微服务项目,CI的过程也都相同,所以创建一个共享runner。

共享runner创建位置位于gitlab首页 > 群组 > 选择一个群组进入 > 构建 > 新建群组Runner,创建后复制token备用。

image-20230719093704680
# 1.1.2 helm部署minio用作runner缓存
# 创建命名空间 infra 。minio和runner都放在这里
kubectl create namespace infra

# 添加minio仓库
helm repo add minio https://helm.min.io/

# 创建生成minio的values.yaml
helm show values minio/minio > values.yaml
1
2
3
4
5
6
7
8

找到并修改minio的values.yaml中的以下内容。

# 账号密码
accessKey: "minio"
secretKey: "minio123"


persistence:
	# 开启
  enabled: true
  # 这里我没写,因为之前已经设置了default storage class
  storageClass: ""
  VolumeName: ""
  accessMode: ReadWriteOnce
  # 大小设置为5G,用来存放缓存够了
  size: 5Gi
resources:
  requests:
    memory: 128Mi
service:
  type: NodePort
  clusterIP: ~
  port: 9000
  nodePort: 32000    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

部署

helm install -f values.yaml minio  minio/minio -n infra
1

然后可以访问 集群ip:32000进去minio的ui页面

# 1.1.3 helm部署runner实例
# 添加gitlab仓库
helm repo add gitlab https://charts.gitlab.io

# 创建生成gitlab-runner的values.yaml
helm show values gitlab/gitlab-runner > values.yaml
1
2
3
4
5

找到并修改gitlab-runner的values.yaml中的以下内容

gitlabUrl: http://10.20.24.20:9080
runnerRegistrationToken: "1.1中获得的token"
rbac:
	create: true
	rules: []
	clusterWideAccess: true
	serviceAccountName: gitlab-runner

runners:
  config: |
    [[runners]]
      [runners.kubernetes]
        namespace = "{{.Release.Namespace}}"
        image = "ubuntu:16.04"
      [runners.cache]
        Type = "s3"
        Shared = true
        [runners.cache.s3]
          AccessKey = "minio"
          SecretKey = "minio123"
          BucketName = "mvnrepo"
          ServerAddress = "minio.infra.svc.cluster.local:9000"
          # minio没有配置https,所以要加上这个配置
          Insecure = true	
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

部署

helm install -f values.yaml gitlab-runner gitlab/gitlab-runner -n infra
1

然后进入gitlab的群组runner页面查看注册状态

# 1.2 新建.gitlab-ci.yml和Dockerfile

项目的根目录下新增.gitlab-ci.yml和Dockerfile

注意是yml不是yaml

stages:
  - package
  - build

package:
  tags:
    - shared
  image: maven:3.8.1-openjdk-17
  stage: package
  script:
    - mkdir $HOME/.m2
    - echo '<settings xmlns="http://maven.apache.org/SETTINGS/1.2.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.2.0 https://maven.apache.org/xsd/settings-1.2.0.xsd">
      <localRepository>${CI_PROJECT_DIR}/.m2</localRepository>
      <mirrors>
      <mirror>
      <id>nexus-private</id>
      <mirrorOf>*</mirrorOf>
      <name>Nexus Private</name>
      <url>http://10.20.24.50:8081/repository/maven-public</url>
      </mirror>
      </mirrors>
      </settings>' > $HOME/.m2/settings.xml
    - mvn package -DskipTests
  artifacts:
    paths:
      - target/*.jar
  # 配置一个存储保存cache。创建一个minio
  cache:
    key: "$CI_PROJECT_DIR"
    paths:
      - $CI_PROJECT_DIR/.m2
    # 执行任务前先下载,任务完成后上传
    policy: pull-push
  rules:
    - if: $CI_COMMIT_TAG


build:
  tags:
    - shared
  stage: build
  image:
    # name: gcr.io/kaniko-project/executor:debug 替换为自己的镜像
    name: 10.20.24.50/library/kaniko-project/executor:debug
    entrypoint: [""]
  script:
    - mkdir -p /kaniko/.docker
    - cp $CI_PROJECT_DIR/config/certs/ca.crt /kaniko/ssl/certs/
    - echo "{\"auths\":{\"${CI_REGISTRY}\":{\"auth\":\"$(printf "%s:%s" "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" | base64 | tr -d '\n')\"}}}" > /kaniko/.docker/config.json
    - echo $CI_REGISTRY $CI_REGISTRY_USER $CI_REGISTRY_PASSWORD
    - echo $CI_PROJECT_DIR $CI_REGISTRY_IMAGE $CI_COMMIT_TAG
    - cat /kaniko/.docker/config.json
    - >-
      /kaniko/executor
      --context "${CI_PROJECT_DIR}"
      --dockerfile "${CI_PROJECT_DIR}/config/Dockerfile"
      --destination "${CI_REGISTRY_IMAGE}:${CI_COMMIT_TAG}"
  rules:
    - if: $CI_COMMIT_TAG
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
FROM openjdk:17-alpine3.14

COPY target/*.jar /app.jar

ENV PROFILE=dev
ENV PORT=8080

EXPOSE 8080

ENTRYPOINT ["java"]
CMD ["-jar", "/app.jar","--spring.profiles.active=${PROFILE}","-Dserver.port=${PORT}"]
1
2
3
4
5
6
7
8
9
10
11
ytg@ytgdeMacBook-Pro archivist % tree -I 'src|target|logs'
.
├── HELP.md
├── config
│   ├── Dockerfile
│   ├── certs
│   │   └── ca.crt
│   └── readme.md
├── .gitlab-ci.yml
└── pom.xml

1
2
3
4
5
6
7
8
9
10
11

# 1.3 gitlab中新增环境变量

上边的.gitlab-ci.yml中用到了自定义变量CI_REGISTRY CI_REGISTRY_USER CI_REGISTRY_PASSWORD CI_REGISTRY_IMAGE

其中CI_REGISTRY CI_REGISTRY_USER CI_REGISTRY_PASSWORD 是群组内公共变量,添加位置位于gitlab首页 > 群组 > 选择一个群组进入 > 设置 > CI/CD > 变量

CI_REGISTRY_IMAGE是项目私有变量,添加位置位于gitlab首页 > 项目 > 选择项目进入 > 设置 > CI/CD > 变量

image-20230719093453986

# 1.4 提交测试

git add .
git commit -m '测试ci'
git tag 1.0.0-testci
git push origin 1.0.0-testci
1
2
3
4
image-20230719093855825

并且cache上传成功

image-20230719094053639

# 2. gitops

# 2.1 新建kustomization文件

ytg@ytgdeMacBook-Pro archivist % tree -I 'src|target|logs'
.
├── HELP.md
├── config
│   ├── Dockerfile
│   ├── certs
│   │   └── ca.crt
│   ├── kustomize
│   │   ├── base
│   │   │   ├── Deployment.yaml
│   │   │   ├── Service.yaml
│   │   │   └── kustomization.yaml
│   │   ├── kustomization.yaml
│   │   └── overlays
│   │       ├── dev
│   │       │   ├── kustomization.yaml
│   │       │   ├── set-profile.yaml
│   │       │   └── set-version.yaml
│   │       └── prod
│   │           ├── kustomization.yaml
│   │           ├── set-profile.yaml
│   │           └── set-version.yaml
│   └── readme.md
├── .gitlab-ci.yml
└── pom.xml
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

其中config/kustomize/base/下是基准文件,config/kustomize/overlays/下是复写文件。

# config/kustomize/kustomization.yaml
resources:
  - overlays/dev
  - overlays/prod
  
# config/kustomize/base/kustomization.yaml
resources:
  - Deployment.yaml
  - Service.yaml

commonLabels:
  app: flowpulse
  
# config/kustomize/overlays/dev/set-profile.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: flowpulse-archivist
spec:
  template:
    spec:
      containers:
        - name: flowpulse-archivist
          env:
            - name: PROFILE
              value: "dev"
            - name: PORT
              value: "8080"
              
# config/kustomize/overlays/dev/set-version.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: flowpulse-archivist
spec:
  template:
    spec:
      containers:
        - name: flowpulse-archivist
          image: 10.20.24.50/flowpulse/archivist:1.0.0
          
# config/kustomize/overlays/dev/kustomization.yaml
resources:
  - ../../base
patchesStrategicMerge:
  - set-profile.yaml
  - set-version.yaml
namePrefix: dev-
namespace: flowpulse-dev
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

# 2.2 测试kustomization文件

brew install kustomize
cd config/kustomize

# 会基于base与overlays生成完整的k8s对象文件,内容过长,不贴出来了
kustomize build 
1
2
3
4
5

# 2.3 ArgoCD(KubeSphere)测试

kubesphere中已经集成了argocd,只需要在ks-installer中enable就可以使用了,然后按照官方文档 (opens new window)创建一个devops的持续部署项目就可以了。

这是我创建之后的Application对象文件,根据git中的dev分支进行同步

image-20230719100606903

然后回到本地的项目目录提交代码到git的dev分支。

git add .
git commit -m 'test gitops'
git push origin dev
1
2
3

回到kubesphere查看同步状态已经同步成功

image-20230719101725651

查看k8s,资源已经进入running状态

ytg@ytgdeMacBook-Pro ~ % kubectl get all -n flowpulse-dev
NAME                                         READY   STATUS    RESTARTS   AGE
pod/dev-flowpulse-archivist-97d5756c-v66nm   1/1     Running   0          10m

NAME                              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/dev-flowpulse-archivist   ClusterIP   10.100.190.2   <none>        80/TCP    10m

NAME                                      READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/dev-flowpulse-archivist   1/1     1            1           10m

NAME                                               DESIRED   CURRENT   READY   AGE
replicaset.apps/dev-flowpulse-archivist-97d5756c   1         1         1       10m
1
2
3
4
5
6
7
8
9
10
11
12

完毕

上次更新: 2023/7/19 10:45:06