您现在的位置是:首页 >其他 >【DevOps】GitOps多环境管理最佳实践 -- 多目录模式网站首页其他
【DevOps】GitOps多环境管理最佳实践 -- 多目录模式
前言
GitOps的多环境是一个棘手的问题,在前面的文章中,我们已经极力劝阻使用多分支的方式管理多个环境,以及环境之间的版本升级。在本文中,我们将详细介绍如何使用同一Git分支上的不同目录来建模GitOps的多环境管理,如何通过简单的文件复制操作来处理环境升级。
更多精彩内容,请关注我的技术微信公众号DevOps365
Helm/Kustomize的启示
在Kubernetes集群中,如果要说最流行的应用部署工具,那必定是helm
了,其次还有kustomize
。 首先来看helm
, 在一个helm Chart
中,会使用一个存有应用部署所需全部参数的values.yaml
文件,来部署一个应用。如果有多个环境,由于每个环境的参数不同,因此需要多个values.yaml
文件,例如:
my-chart/
├── Chart.yaml
├── charts/
├── environments/
│ ├── dev/
│ │ └── values.yaml
│ ├── prod/
│ │ └── values.yaml
│ └── staging/
│ └── values.yaml
├── templates/
│ ├── deployment.yaml
│ └── service.yaml
└── values.yaml
其中,应用主目录下的values.yaml
存放各环境通用的参数值,而在environments
目录下的三个以环境名命名的子目录下的values.yaml
中则存放了根据环境不同而不一样的参数值。在部署三个环境时,应分别使用下面的命令:
# 部署到production环境
helm install my-app my-chart --values values.yaml --values environments/prod/values.yaml
# 部署到staging环境
helm install my-app my-chart --values values.yaml --values environments/staging/values.yaml
# 部署到dev环境
helm install my-app my-chart --values values.yaml --values environments/dev/values.yaml
我们再来看kustomize
是怎么管理多个环境的部署的,下面是一个目录结构示例:
my-app/
├── base/
│ ├── deployment.yaml
│ ├── kustomization.yaml
│ └── service.yaml
└── overlays/
├── dev/
│ ├── kustomization.yaml
│ └── patch.yaml
├── staging/
│ ├── kustomization.yaml
│ └── patch.yaml
└── prod/
├── kustomization.yaml
└── patch.yaml
跟helm
类似,各个环境通用的manifests
放在base
目录下,而各环境不同的manifests, 则放到overlays
目录中对应环境命令的目录下的patch.yaml
中, 让我们来看看overlays/dev/kustomization.yaml
的内容:
# overlays/dev/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
- patch.yaml
namespace: my-app-dev
根据其配置,表示会对base
目录下的各个文件与patch.yaml
的内容进行合并后再部署, 让我们来看看base/deployment.yaml
:
# base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 1
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: my-registry/my-app:latest
ports:
- containerPort: 8080
再来看看overlays/dev/patch.yaml
,overlays/staging/patch.yaml
, overlays/prod/patch.yaml
# overlays/dev/patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 2
# overlays/staging/patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 5
# overlays/prod/patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 10
这样配置的话,各个环境间不同的参数也就一目了然了,那便是replicas
的差别, dev
环境是2, staging
环境是5, 而prod
环境是10。
由此可见, 无论是helm
还是kustomize
, 都是通过不同的文件或目录来区分不同的环境,并且它们的应用中,也并没有Git分支的用法,也感知不到Git的代码合并。通过这两个例子,或许可以给我们一些启发,怎么来管理多环境的部署与升级。
更多精彩内容,请关注我的技术微信公众号DevOps365
分析并归类应用配置
在创建目录结构之前,我们需要了解我们的应用程序所需的配置,因为不同的配置,他们的重要性其实是不同的。 在 Kubernetes下的应用程序中,我们可以将环境配置分为以下几个类别:
- 应用版本:以容器标签的形式表示。这是 Kubernetes 部署清单中最重要的配置,对于环境升级来说至关重要。我们通常都是通过更改容器镜像的版本来完成环境升级。
- Kubernetes 特定设置:这包括应用程序的副本数以及其他 Kubernetes 相关信息,如资源限制、健康检查、持久卷、亲和规则等等。
- 静态的业务设置:这是一组与 Kubernetes 无关但与应用程序业务相关的配置, 且这些配置一般与环境有关,这些设置只需在每个环境中定义一次,不会随着环境升级而改变。它们可能包括外部 URL、内部队列大小、UI 默认值、身份验证配置文件等等。例如,我们可能希望生产环境使用 myapp.com,而非生产环境使用 myapp.staging.com, 这些设置不会因为应用升级而变化。
- 非静态的业务设置:这也是与业务相关的设置,但是它们是会随着环境升级而改变的。例如一些调优参数,推荐引擎参数、可用的比特率编码以及其他特定于业务的设置。
依据上面的分类方式,我们必须清楚的了解自己的应用所需的各类配置,才能方便我们去将不同的配置放到合适的目录和文件中,特别是那些属于非静态的业务设置,会随着环境升级而频繁改变,准确的识别这类配置更是重中之重。例如以下两个场景:
qa
环境中的应用从v1.0
升级到v1.1
,只有源代码改动,那么久只需要修改镜像的版本号即可完成升级;staging
环境的应用从v2.1
升级到v2.2
, 源代有改动,其一个配置recommend_batch_size
也有改动, 那就不仅需要修改版本号,还要修改把qa
环境上的这个配置参数升级到staging
中去。
妥善的归类这些配置,并放到合适的位置,会使频繁的环境升级所需要的文件修改操作变得更少,更可靠,更安全。
更多精彩内容,请关注我的技术微信公众号DevOps365
复杂的多环境管理示例
让我们来看一个实际的例子, 这个示例中,我们将使用kustomize
, 因为在之前的文章中我们已经说明了helm
与GitOps的结合并不十分完美,用kustomize
会让事情变得更加简单一些。QA/Staging/Production 老三样已经不新鲜了,总是用这种简单无聊的场景举例,也不务实,因为实际的场景往往更加的复杂。 假设某巨大的公司有 5 种(注意是5种,不是5个)不同的环境,分别是:
- 开发环境(Dev)
- 测试(Test)
- QA
- STAGING
- PROD
其中, Staging和Production分别在欧洲(EU),亚洲(AP)都有两个环境,分别是X86架构和ARM架构,Dev和Test环境分别有两个环境(X86和ARM架构),那么总共就又13个环境。分别如下:
- DEV-X86
- DEV-ARM
- TEST-X86
- TEST-ARM
- QA
- STAGING-EU-X86
- STAGING-EU-ARM
- STAGING-AP-X86
- STAGING-AP-ARM
- PROD-EU-X86
- PROD-EU-ARM
- PROD-AP-X86
- PROD-AP-ARM
那么我们可以设计如下的目录结构:
my-app/
├── base/
│ ├── deployment.yaml
│ ├── kustomization.yaml
│ └── service.yaml
├── env/
│ ├── dev-x86/
│ ├── dev-arm/
│ ├── test-x86/
│ ├── test-arm/
│ ├── qa/
│ ├── staging-ap-x86/
│ ├── staging-eu-x86/
│ ├── staging-ap-arm/
│ ├── staging-eu-arm/
│ ├── prod-ap-x86/
│ ├── prod-eu-x86/
│ ├── prod-ap-arm/
│ └── prod-eu-arm/
└── variants/
├── x86/
├── arm/
├── eu/
├── ap/
├── non-prod/
└── prod/
在variant目录下,我们对一些配置进行了分类,如根据环境架构,分为x86架构下公共的配置,arm架构下公共的配置。根据region, 分为了eu地区公共的配置, ap地区公共的配置。根据是否是生产环境,分为了non-prod环境公共的配置,和prod环境公共的配置。让我们来看一下prod和non-prod环境的配置差别
#variants/prod/deployment.yaml |#variants/non-prod/deployment.yaml
---
apiVersion: apps/v1 |apiVersion: apps/v1
kind: Deployment |kind: Deployment
metadata: |metadata:
name: demo-deployment |name: demo-deployment
spec: |spec:
template: | template:
spec: | spec:
containers: | containers:
- name: demo-app | - name: demo-app
env: | env:
- name: ENV_TYPE | - name: ENV_TYPE
value: "production" | value: "non-production"
- name: DB_ADDRESS | - name: DB_ADDRESS
value: "prod-db.example.com" | value: "non-prod-db.example.com"
- name: DB_USER | - name: DB_USER
value: "prod_username" | value: "nonprod_username"
- name: DB_PASSWORD | - name: DB_PASSWORD
value: "prod_password" | value: "nonprod_password"
上面的配置中,prod环境和non-prod环境的环境类型不同,并且使用的数据库地址和数据库用户名密码都不同,一般来说,这些配置不会随着环境升级而变动的,它们将在整个软件开发周期中保持不变。
那么,让我们来看一下env/dev-x86/kustomization.yaml
的内容大概应该是怎样的呢:
# env/dev-x86/kustomization.yaml
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: dev
namePrefix: dev-x86-
resources:
- ../../base
components:
- ../../variants/non-prod
- ../../variants/x86
patchesStrategicMerge:
- deployment.yml
- version.yml
- replicas.yml
- settings.yml
上面的配置中,分成了三个部分,resources
中包含了所有环境的公共和默认配置,components
中包含了该环境所属环境分类的一些公共配置(如非生产环境公有的配置和x86环境公有的配置), patchesStrategicMerge
中则是该具体环境中特有的配置, 如version.yml
和 replicas.yml
,顾名思义,分别包含了镜像版本和副本数的配置,例如:
# env/dev-x86/version.yml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-deployment
spec:
template:
spec:
containers:
- name: demo-app
image: docker.io/demo-app:2.0
# env/dev-x86/replicas.yml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-deployment
spec:
replicas: 3
另一些与应用相关的配置,需要随着环境中应用版本升级而更新的,我们存到settings.yml
中, 例如:
# env/dev-x86/settings.yml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-deployment
spec:
template:
spec:
containers:
- name: demo-app
env:
- name: IDLE_TIME
value: "10s"
- name: MEM_LIMIT
value: "1024kb"
- name: TIMEOUT
value: "5s"
更多精彩内容,请关注我的技术微信公众号DevOps365
开始环境部署
通过这个例子想必大家已经对怎么组织复杂环境的配置有一定的概念了。现在我们可以开始部署环境了。如果使用诸如ArgoCD一类的GitOps工具,要部署dev-x86
环境,我们只需要将应用的source
指定为env/dev-x86
目录即可, 其中的kustomization.yml
文件将被自动检测到并被部署。当然,我们也可以用kustomize cli
来生成待部署的清单,如下可生成三个环境将要被部署的清单:
kustomize build envs/dev-x86
kustomize build envs/qa
kustomize build envs/staging-ap-x86
当然了,用这些生成的清单,我们也可以很容易的使用kubectl
命令来完成应用的部署。但为了遵循GitOps的原则,我们应该避免手动的执行kubectl
命令,而应该尽量让GitOps工具帮我们自动的部署。
更多精彩内容,请关注我的技术微信公众号DevOps365
查看环境间配置差异
可以用kustomize cli
生成各环境完整的部署清单,然后用vimdiff
命令或IDE
查看它们之间的配置差异。
kustomize build envs/dev-x86/> /tmp/dev-x86.yml
kustomize build envs/qa/ > /tmp/qa.yml
kustomize build envs/staging-ap-arm/ > /tmp/staging-ap-arm.yml
vimdiff /tmp/dev-x86.yml /tmp/qa.yml /tmp/staging-ap-arm.yml
更多精彩内容,请关注我的技术微信公众号DevOps365
环境升级步骤
情景一
从dev-x86
环境升级到qa
环境:
- 复制版本配置文件:
cp env/dev-x86/version.yml env/qa/version.yml
- 提交代码,合并代码到主分支
情景二
从dev-x86
环境升级到qa
环境,再升级到staging-ap-x86
环境,包含应用相关配置的更改:
- 复制版本配置文件:
cp env/dev-x86/version.yml env/qa/version.yml
cp env/dev-x86/settings.yml env/qa/settings.yml
- 提交代码,合并代码到主分支
cp env/qa/version.yml env/staging-ap-x86/version.yml
cp env/qa/settings.yml env/staging-ap-x86/settings.yml
- 提交代码,合并代码到主分支
情景三
修改所有arm
环境的公共配置:
- 在
variants/arm/
目录下对应的配置文件中修改配置 - 提交代码,合并到主分支
情景四
修改所有ap
环境的公共配置:
- 在
variants/ap/
目录下对应的配置文件中修改配置 - 提交代码,合并到主分支
更多精彩内容,请关注我的技术微信公众号DevOps365
多目录模式的优点
在上一篇文章中,我们提到了分支模式的几大问题,其中包括提交顺序不合需要,容易带来意料之外的修改等。使用多目录模式,则可以完美的避开这些陷阱。
- 使用多目录模式的话,环境升级跟
commit
是无关的,我们通过文件复制操作来升级环境,拷贝我们需要的内容,环境升级的结果就跟commit
完全没有关系,只与我们复制的文件内容有关系; - 通过对配置的分类,我们可以避免得到一些意料之外的更新,例如,我们负责
version.yaml
, 其中就只会包含应用镜像版本的变更,而不会改变应用的副本数; - 没有了分支,也就用不着
cherry pick
这些操作来选择我们想要的commit
, 简单的文件复制操作就能代替这个作用; - 文件复制操作可以自由的在各个环境的目录间进行,而不用担心各个环境的层级关系,可以从上级环境向下级环境更新配置,而不用担心更改后带来其他一些功能上的变化。这样也解决了上一篇文章中提到的配置漂移的问题;
- 可以通过
vimdiff
等工具方便的对比各环境配置的整体差异; - 随着环境的变多,多目录模式的复杂度是几乎不变的。多分支模式下,20个环境就需要20个分支,而多目录模式下,20个环境还是只需要一个分支,集中管理了所有环境的配置。GitOps工具扫描的也仅仅是某个环境对应的目录,不会增加扫描的压力;
出于安全策略的原因,很多团队会要求将production
和non-production
的repo分离开,这当然是可以理解的。这样的话,多目录模式确实会变得复杂一些,这样的话,我们会把production
环境单独放到一个仓库, 所有的non-production
环境放到另一个仓库。即便如此,我们的环境复杂度也是固定的,永远是2个仓库。
更多精彩内容,请关注我的技术微信公众号DevOps365
总结
多分支模式在管理复杂环境时会遇到很多棘手的问题,而多目录模式则基本解决了这些问题。本文列举了一个复杂环境的目录结构设计示例以供参考,再次墙裂建议大家在GitOps的实践中采用多目录模式来管理多个环境。
更多精彩内容,请关注我的技术微信公众号DevOps365