亚马逊AWS官方博客

关于Amazon EKS基于Gitlab的CICD实践二 基础架构和应用架构创建篇

关于Gitlab的CI/CD的实践具体分成如下的内容,其中一和二已经在上面一篇 关于Gitlab的CICD的实践Gitlab的部署和配置篇 中介绍完成了。

(一)部署的架构
(二)Gitlab的部署和配置
(三)基础架构的部署
(1) Amazon RDS 的创建
(2) Amazon EKS的创建
(四)应用架构的部署
(1) Amazon RDS 数据库中数据的部署
(2) Amazon EKS集群中应用的部署
在Gitlab的部署和配置完成后,本篇将介绍(三)基础架构的部署和(四)应用架构的部署。Gitlab 可以在代码仓库的根目录定义一个名为 .gitlab-ci.yml 的文件。这个文件是你定义你的CI/CD pipeline的地方。在开始创建和定义你的 .gitlab-ci.yml 之前,我们需要了解GitLab CI/CD中一些概念来描述和运行你的Gitlab CI/CD pipeline。

(三)基础架构的部署

1)pipeline(流水线)

是持续集成、交付和部署的顶级组件。Pipeline由以下部分组成:
job(工作),它定义了要做什么。例如,编译或测试代码的工作。
stage(阶段),定义何时运行作业。例如,在编译代码的stage之后运行测试的stage。
使用stages来定义包含一组job,而stages又被定义在pipeline(流水线)中。在pipeline(流水线)中使用stage来定义某job是那个阶段的一部分。stage的顺序定义了作业的执行顺序。
job是由runner(运行器)执行的。如果有足够多的并发runner,同一stage的多个job可以并行执行。如果一个stage的所有job都成功了,pipeline就会进入下一个stage。如果一个stage中的任何job失败了,下一个stage就不会被执行,pipeline就会提前结束。一般来说,pipeline是自动执行的,一旦创建就不需要干预。然而,有些时候,你可以设定pipeline中的有些阶段是手动来执行,比如删除创建的资源。
例如如下示例中

此示例中定义了三个stages – compile,test,package,同一个stage(package)中的多个job(pack-gz, pack-iso)可以并行执行。

此示例为此次实践中创建EKS Cluster的流水线,其中也定义了三个stages – build,deploy,destroy,同一个stage的所有job都成功了,pipeline就会进入下一个stage。stage的顺序定义了作业的执行顺序。
Pipeline是GitLab中CI/CD的基本构建块。有三种主要方式来构建pipeline,每一种都有自己的优势。如果需要,这些方法可以混合使用。
Basic(基本型): 适合于简单的项目,所有的配置都在一个容易找到的地方。
Directed Acyclic Graph(有向无环图): 适合于需要高效执行的大型复杂项目。
Child/Parent(子/父流水线): 适合单体项目和有很多独立定义的组件的项目。
本实践就是结合了 Child/Parent 和 Directed Acyclic Graph。
有向无环图流水线: 如果效率对你来说很重要,你希望所有的东西都能尽可能快地运行,你可以使用有向无环图(DAG)。使用needs关键字来定义job之间的依赖关系。当GitLab知道你的工作之间的关系时,它可以尽可能快地运行一切,甚至在可能的情况下跳过后续阶段。

# Parent gitlab-ci file, which trigger two CICD steps for AWS EKS cluster and RDS for mysql creation
# And it's also with two CICD steps for applications on EKS and data updating on RDS for mysql

stages:
- trigger-modules

# child gitlab pipeline for EKS cluster creation
EKS cluster creation:
  stage: trigger-modules
  trigger:
    include: infra/eks-cluster/eks-gitlab-ci.yml
  only:
    changes:
    - infra/eks-cluster/*

# child gitlab pipeline for RDS for mysql creation
RDS for mysql creation:
  stage: trigger-modules
  trigger:
    include:  infra/rdsmysql/rdsreal/rds-gitlab-ci.yml
  only:
    changes:
    - infra/rdsmysql/*
    - infra/rdsmysql/**/*

# child gitlab pipeline for data updating on RDS for mysql
RDS for app running:
  stage: trigger-modules
  trigger:
    include:  app/rds/app-rds-gitlab-ci.yml
  only:
    changes:
    - app/rds/*

# child gitlab pipeline for applications on EKS
EKS for app running:
  stage: trigger-modules
  trigger:
    include:  app/eks/app-eks-gitlab-ci.yml
  only:
    changes:
- app/eks/*    

/父流水线: 上面是本实践中位于根目录中 .gitlab-ci.yml 文件的内容 ,通过 trigger 关键字来使用子/父流水线。它将Gitlab CICD流水线配置分离成多个文件,使pipeline变得简单和利于理解。你也可以将其与以下内容结合起来。
rules 关键字。例如,只有指定目录的内容(代码)发生变化时,才会触发子流水线。如上实例中的only子句。
include 关键字。在指定具体的子流水线需要执行的文件。

2)CI/CD变量是环境变量的一种

你可以用它们来 (1) 控制作业和流水线的行为。(2) 存储你想重复使用的值。(3) 避免在你的.gitlab-ci.yml文件中的硬编码值。本实践,我创建了全局环境变量和在project中的子gitlab-ci.yml文件中定义变量。

3)job artifact

job可以输出一个文件和目录的内容,这种输出被称为job artifact,你可以通过使用GitLab用户界面或API下载job artifact。
为了Gitlab的CI/CD能正常的执行,在准备好了从Github上拉取的代码,并push到自建的Gitlab上。以及配置Gitlab 的环境,现在还需要设置环境变量,因为如下的Gitlab CICD 子流水线使用了这些环境变量。
infra/eks-cluster/eks-gitlab-ci.yml
infra/rdsmysql/rdsreal/rds-gitlab-ci.yml
app/eks/app-eks-gitlab-ci.yml
这三个都使用如下的环境变量
- 'AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}'
- 'AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}'
- 'AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION}'

app/rds/app-rds-gitlab-ci.yml
有如下的环境变量的需求
$DB_HOST_MASTER
$DB_USER_MASTER
$DB_PASSWORD_MASTER
$DB_SCHEMA
在Gitlab设置全局环境变量,Menu-Admin-Settings-CICD-Variables-Add variable


按如上步骤创建好AWS AKSK 和 RDS 等环境变量。其中RDS的数据信息需要等到RDS for mysql创建好后在设置。最后的设置后的信息如下

在Gitlab中的gitlabcicd project中有如下内容

1)目录(infra): 通过Gitlab CICD和Terraform自动化的创建和部署亚马逊云科技EKS Cluster和RDS for Mysql. 偏基础设施的团队会更关注这一点.
2)目录(app): 再通过Gitlab CICD结合AWS EKS,AWS ECR,Docker和Liquibase来达成应用系统的版本迭代和发布. 偏业务的团队会更关注这一块.
3)文件(.gitlab-ci.yml): Gitlab CICD的主配置文件,来完成主要的CICD的逻辑调度工作.
4)文件(create_bucket.sh): 因为Terraform作为IaC的工具,需要通过状态文件了解Infrastructure状态,所以需要一个外置存储来存放此状态文件.此shell脚本是来创建S3存储桶和相应的Terraform的状态文件.
infra目录中包含Terraform代码用于自动化的创建EKS Cluster和RDS for Mysql.而Terraform是一个由HashiCorp创建的开源基础设施即代码(IaC)软件工具。用户使用被称为HashiCorp配置语言的声明性配置语言,或可选的JSON来定义和提供数据中心基础设施。更多关于Terraform的信息可以访问如下link:
https://learn.hashicorp.com/terraform?utm_source=terraform_io&utm_content=terraform_io_hero
使用Git将Terraform编写的IaC的代码提交到Gitlab管理的代码仓库中,并做版本控制,另外结合Gitlab CICD自动化的完成基础设施的部署。因为Terraform编写的IaC的代码不是本文的重点,所以不做过多讨论。如上面描述的因为Terraform作为IaC的工具,需要通过状态文件了解Infrastructure状态,而执行Terraform代码的executor(执行器)为docker,Terraform状态文件会随着docker生命周期结束而丢失。所以需要一个外置存储来存放此状态文件。shell脚本(create_bucket.sh)是来创建S3存储桶和相应的Terraform的状态文件。
# 在您的workstation/笔记本执行如下命令
cd ~
cd gitlabcicd
bash ./create_bucket.sh
aws s3 ls s3://jerry-terraform-states/

替换的s3桶名,可以使用如下命令替换:
sed -i ‘s/^Bucket_Name=\(.*\)/Bucket_Name=your_bucket_name/’ create_bucket.sh
根据代码仓库的根目录下 .gitlab-ci.yml 文件的定义,触发自动部署infra资源(EKS cluster,RDS),当然也包括其他相关资源,比如VPC,子网和安全组等。需要修改或添加如下指定位置中的文件并使用Git提交,进而来触发pipeline

我编辑了上面两个目录中的README.md文件,再执行如下
git add .
git commit -m "push"
git push -u origin main
CICD-Pipelines查看触发的流水线和jobs


可以看到job已经顺利执行完成。

EKS集群创建完成后,Destroy stage,我设置成为是手动执行,也就是说,如果你希望删除EKS集群,可以通过点击Destroy旁的播放按钮来触发。

同样RDS创建完成,Destroy也是可以手动执行的。

EKS集群和RDS创建完成后,有生成artifact,可以通过job页面下载

其实您也可以修改EKS和RDS的名称以及数据库user名称和密码,具体修改的位置如下。

EKS修改集群名称和VPC IP地址网段的位置:

infra/eks-cluster/vpc.tf 

variable "general_name" {
  default     = "cicdeks"
  description = "general name"
}
…

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "3.2.0"

  name                 = local.vpc_name
  cidr                 = "10.98.0.0/18"
  azs                  = data.aws_availability_zones.available.names
  private_subnets      = ["10.98.1.0/24", "10.98.2.0/24", "10.98.3.0/24"]
  public_subnets       = ["10.98.4.0/24", "10.98.5.0/24", "10.98.6.0/24"]
  enable_nat_gateway   = true
  single_nat_gateway   = true
  enable_dns_hostnames = true
…

RDS修改database名称和VPC IP地址网段的位置:
infra/rdsmysql/rdsreal/main.tf 

variable "general_name" {
  default     = "cicdrds"
  description = "general name"
}
...

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 2"

  name = local.vpc_name
  cidr = "10.99.0.0/18"
  azs              = ["${local.region}a", "${local.region}b", "${local.region}c"]
  public_subnets   = ["10.99.0.0/24", "10.99.1.0/24", "10.99.2.0/24"]
  database_subnets = ["10.99.7.0/24", "10.99.8.0/24", "10.99.9.0/24"]
...

module "db" {
  source = "../"

  identifier = local.identifier

  # All available versions: http://docs.thinkwithwp.com/AmazonRDS/latest/UserGuide/CHAP_MySQL.html#MySQL.Concepts.VersionMgmt
  engine               = "mysql"
  engine_version       = "8.0.20"
  family               = "mysql8.0" # DB parameter group
  major_engine_version = "8.0"      # DB option group
  instance_class       = "db.t3.large"

  allocated_storage     = 20
  max_allocated_storage = 100
  storage_encrypted     = false

  name     = local.name
# you have to modify the username and password below
  username = "your_db_user_name"
  password = "your_db_user_password"

(四)应用架构的部署

在部署应用之前,我们可以通过如下步骤获得一些关于EKS cluster和RDS的信息


这两个artifact,一个是描述EKS集群的信息,一个描述RDS的信息。如果你更改了EKS集群或RDS的名称等信息,请记录下这些内容。修改Gitlab全局环境变量:
DB_HOST_MASTER      cicdrds-eoca.*******.rds.cn-northwest-1.amazonaws.com.cn
DB_USER_MASTER      your_db_user_name
DB_PASSWORD_MASTER  your_db_user_password
DB_SCHEMA           cicdrds

另外,请按照你自己的设定修改app/eks/app-eks-gitlab-ci.yml

1)环境变量

特别是下图中的CONTAINER_NAME,CONTAINER_IMAGE,ECR_STR,EKS_CLUSTER_PREFIX

2)build应用docker镜像

另外,在 app/eks/app-eks-gitlab-ci.yml 中已经定义了在docker作为executor(执行器),其中也包含了build应用为docker镜像,并推送到ECR的私有镜像仓库中,也包含了安装awscli,eksctl,kubectl工具,并创建kube config的步骤。
此处为build应用为docker镜像,并推送到ECR的私有镜像仓库中:

3)创建ingress

此处包含了安装awscli,eksctl,kubectl工具,并创建kube config的步骤,当然为了发布web服务,其中也包含了创建ingress的步骤。

4)发布应用

最终是通过yaml文件来部署应用,当然也可用通过helm来部署

而对于RDS数据库中内容的设定,使用了Liquibase作为数据库的版本控制,Liquibase作为一个数据库版本管理工具,它实现了(1)数据库升级, (2)数据库回滚, (3)版本标记。这样能很好的满足在业务系统发展的过程中,对数据库中表结构以及表数据的管理做到版本化。
它的几个核心概念:版本号,管理的数据,差异比较,版本回滚, 版本号由开发人员来维护,使用 author + id的格式。
Liquibase管理的数据最小单元为 changeSet,changeSet被包含在changelog中,changelog 是Liquibase版本控制的核心,Liquibase 通过有序的 changelog 罗列你对数据库的更改,就相当于你对数据变更的日志。Liquibase 使用这个变更日志来审计你的数据库,并执行任何还没有应用到目标数据库的变更操作。changelog支持5种格式来编写 — sql,xml,yaml,json,other 来编写。更多信息可以查看如下链接:

https://docs.liquibase.com/concepts/basic/home.html
因为我们使用Liquibase docker镜像作为executor(执行器),所不需要单独安装和部署,app/rds/app-rds-gitlab-ci.yml具体内容如下。
Liquibase docker镜像作为executor(执行器):

Liquibase执行数据库数据的变更:


我们通过修改 app/rds/README.md 和app/eks/README.md 来触发应用架构的部署。

等EKS部署完测试的应用,在你的workstation执行如下命令
aws eks list-clusters
aws eks update-kubeconfig --name cicdeks-cluster-***
kubectl get ingress --all-namespaces

查看通过ingress暴露的测试应用

此时,你假如修改你的应用代码(例如app/eks/app.go),将触发新一轮的应用CICD,当然你也可以加入更多的test(单元测试和集成测试的内容)内容到app/eks/app-eks-gitlab-ci.yml中。
至此,整个关于Gitlab的CICD的实践就完成了。在整篇博客中我们回顾的DevOps,CICD,GitOps的概念,同时又通过Git,Gitlab结合Terraform, Liquibase, Amazon Elastic Kubernetes Service(EKS), Amazon Relational Database Service (RDS), Amazon Elastic Container Registry(ECR), Docker实践了CICD,在这里也是GitOps。很好的展现了基于Gitlab的CICD的实践。

参考材料:

https://www.infoworld.com/article/3271126/what-is-cicd-continuous-integration-and-continuous-delivery-explained.html
https://blog.container-solutions.com/fluxcd-argocd-jenkins-x-gitops-tools
https://thinkwithwp.com/devops/
https://about.gitlab.com/topics/gitops/
https://www.cloudbees.com/gitops/what-is-gitops
https://about.gitlab.com/is-it-any-good/
https://about.gitlab.com/stages-devops-lifecycle/continuous-integration/
https://www.terraform.io/
https://www.liquibase.org/
https://www.amazonaws.cn/eks/?nc1=h_ls
https://www.amazonaws.cn/rds/?nc1=h_ls
https://www.amazonaws.cn/ecr/?nc1=h_ls
https://www.docker.com/
https://helm.sh/

本篇作者

金忠敏

AWS解决方案架构师,现在专注于云计算解决方案和架构的工作。具有超过15年的IT从业经验,曾从事软件开发,售后支持,系统交付,售前等工作。参与过很多大型项目架构设计和实施交付。

肖元君

AWS解决方案架构师,负责基于AWS云计算方案的架构咨询和设计实现,同时致力于数据分析与AI的研究与应用。