上一篇:用AWS EC2从零搭建Kubernetes和ArgoCD
前一篇里,我们在 AWS EC2
上从无到有,搭建了 Kubernetes
集群和 ArgoCD
。因为终究缺少 CI
环境以实现真正的实时持续集成,这一篇将手把手,在
AWS EC2
上搭建 Jenkins
实现一个 GitOps CI/CD
工作流。
预备 #
准备 EC2
实例
#
使用 Amazon Linux 2 AMI。确保
- 安全组(
Security Group
)开放8080
端口。 - EC2 的 IAM 角色有对 ECR 的读和写权限。一般是使用
AmazonEC2ContainerRegistryFullAccess
策略。
SSH
登录 EC2
#
1ssh -i <your-key.pem> ec2-user@<EC2-Public-IP>
更新包管理器 dnf
#
1sudo dnf update -y
安装 wget
#
1sudo dnf install wget -y
安装 git
#
1sudo dnf install git -y
安装 docker
#
1sudo dnf install docker -y
2sudo systemctl start docker
3sudo systemctl enable docker
4sudo usermod -aG docker $USER
为了避免每次使用
docker
命令都需要sudo
,将当前用户添加到docker
用户组。
安装 Java
#
1sudo dnf install java-17-amazon-corretto -y
2sudo dnf install fontconfig -y
因为是在 AWS EC2 上安装,所以使用的是 Amazon Corretto JDK。
fontconfig 是 Java 运行时所需的字体配置。
安装 Jenkins #
添加 Jenkins 仓库 #
1sudo wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo
2sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io-2023.key
安装 #
1sudo dnf install jenkins -y
启动服务 #
1sudo systemctl start jenkins
2sudo systemctl enable jenkins
查看服务状态 #
1sudo systemctl status jenkins
应当看到类似如下输出,服务应当处于 active (running)
状态。
● jenkins.service - Jenkins Continuous Integration Server
Loaded: loaded (/usr/lib/systemd/system/jenkins.service; disabled; preset: disabled)
Active: active (running) since Thu 2025-01-16 09:13:46 UTC; 7s ago
Main PID: 976120 (java)
Tasks: 53 (limit: 4565)
Memory: 591.4M
CPU: 24.742s
CGroup: /system.slice/jenkins.service
└─976120 /usr/bin/java -Djava.awt.headless=true -jar /usr/share/java/jenkins.war --webroot=/var/cache/jenkins/war --httpPort=8080
添加 Jenkins 用户到 Docker 用户组 #
1sudo usermod -aG docker jenkins
2sudo systemctl restart jenkins
3sudo systemctl restart docker
对于使用 Docker 进行镜像构建的 Jenkins 任务,这一步很重要。
配置 Jenkins #
获取初始密码 #
1sudo cat /var/lib/jenkins/secrets/initialAdminPassword
访问并初始化 #
- 在浏览器中输入
http://<EC2-Public-IP>:8080
,然后输入初始密码,即可进入 Jenkins UI。 - 选择安装推荐插件。
- 创建管理员账户。
- 配置 Jenkins URL。(默认即可)
完成后,进入 Jenkins 主界面。
预备代码仓库 #
应用仓库 #
假设我们已经有一个可以被 Docker
构建的极简 hello-world HTTP
服务应用,代码仓库地址为以下。
https://github.com/username/hello-world.git
这个仓库只管理应用代码,不包含部署配置。
部署配置仓库 #
我们还需要一个仓库用来管理用于 Kubernetes/ArgoCD
消费的部署配置文件,这个仓库地址为
https://github.com/username/hello-world-deployment.git
仓库的文件结构为
./staging/deployment.yml
./production/deployment.yml
这个仓库只管理部署配置,不包含应用代码。
两个
manifest
文件分别用于staging
和production
环境。
部署配置文件 #
以 staging
环境的 deployment.yml
文件为例。一般来说,这个文件包含以下几个部分,即以下几个 Kind
:
Section | Description |
---|---|
Deployment | 管理应用生命周期的资源,它定义了应用的副本数、容器镜像、更新策略等。 |
Service | 定义如何在集群内或外部访问 Pods。它充当了 Pods 的访问入口,能够提供负载均衡和端口映射。 |
Namespace | 用于资源逻辑隔离 |
以下是一个基础的 deployment.yml
文件。
1# Create a namespace
2apiVersion: v1
3kind: Namespace
4metadata:
5 name: YOUR_NAMESPACE
6---
7## define the deployment
8apiVersion: apps/v1
9kind: Deployment
10metadata:
11 name: YOUR_DEPLOYMENT_NAME
12 namespace: YOUR_NAMESPACE
13 labels:
14 app: YOUR_LABEL
15spec:
16 replicas: 2
17 selector:
18 matchLabels:
19 app: YOUR_LABEL
20 template:
21 metadata:
22 labels:
23 app: YOUR_LABEL
24 spec:
25 imagePullSecrets:
26 - name: ecr-pull-secret
27 affinity:
28 nodeAffinity:
29 requiredDuringSchedulingIgnoredDuringExecution:
30 nodeSelectorTerms:
31 - matchExpressions:
32 - key: kubernetes.io/arch
33 operator: In
34 values:
35 - arm64
36 - amd64
37 containers:
38 - name: YOUR_CONTAINER_NAME
39 image: YOUR_AWS_ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com/hello-world:latest
40 ports:
41 - name: http
42 containerPort: 8080
43 imagePullPolicy: Always
44 nodeSelector:
45 kubernetes.io/os: linux
46---
47## define the service
48apiVersion: v1
49kind: Service
50metadata:
51 name: YOUR_SERVICE_NAME
52 namespace: YOUR_NAMESPACE
53 labels:
54 app: YOUR_LABEL
55spec:
56 selector:
57 app: YOUR_LABEL
58 ports:
59 - port: 80
60 targetPort: 8080
61 nodePort: 30000
62 type: NodePort
重点字段说明:
请根据实际情况替换
YOUR_
开头的字段。replica: 副本数,根据实际情况调整。
imagePullPolicy: 镜像拉取策略,
Always
表示每次都拉取最新镜像,IfNotPresent
表示只有本地没有时才拉取。nodePort: 用于外部访问的端口,即可以用
http://<EC2-Public-IP>:30000
访问服务。
关于 imagePullSecrets
#
配置中看到到 imagePullSecrets
字段,是用于从让 kubernetes
使用 docker
从 ECR
拉取镜像时的校验,需要提前创建,这非常重要。
先让 Docker 登录到 ECR。
1aws ecr get-login-password --region <your_aws_region> | docker login --username AWS --password-stdin <your_aws_account>.dkr.ecr.ap-northeast-1.amazonaws.com
然后根据已有的 ~/.docker/config.json
文件为 kubernetes
创建 secret
。
1kubectl create secret generic regcred \
2 --from-file=.dockerconfigjson=<path/to/.docker/config.json> \
3 --type=kubernetes.io/dockerconfigjson \
4 --namespace <namespace_of_your_application>
其中
regcred
是一个自定义的名称,可以根据实际情况替换,但必须保证和deployment.yml
中的一致。
--namespace
必须和deployment.yml
中的一致。参考:官方文档
检查创建好的 secret。
1kubectl get secret regcred -n <namespace_of_your_application>
删除现有的 secret 的命令:
1kubectl delete secret <secret_name> -n <namespace_of_your_application>
以上命令或需周期性地执行,以保证
kubernetes
能够正常拉取镜像。
配置 Pipeline #
Pipeline的关键是 Jenkinsfile
,它定义了整个 CI/CD
流程。通常对于 staging
(预览环境)和 production
(生产环境)有不同的流程。
环境 | 触发条件 | 镜像版本 | 部署方式 |
---|---|---|---|
staging | push 到目标分支 |
分支的 HEAD commit hash |
由CI自动触发 ArgoCD 的同步命令 |
production | tag 并 release |
同 tag |
人工手动在 ArgoCD 上触发同步 |
以下是针对 staging
环境的 Jenkinsfile
。
1pipeline {
2 agent any
3 // 定义环境变量以便在整个流程中引用
4 // 这里我们用 `AWS` 的 `ECR`(`Elastic Container Registry`)作为 `Docker` 镜像仓库。
5 environment {
6 ECR_REGISTRY = '733089366385.dkr.ecr.ap-northeast-1.amazonaws.com'
7 ECR_REPOSITORY = 'hello-world'
8 REGION = 'ap-northeast-1'
9 }
10 stages {
11 stage('Checkout') {
12 steps {
13 // 从 GitHub 仓库拉取代码
14 // 目标分支是 `main`
15 git branch: 'main', url: 'https://github.com/username/hello-world.git',
16 // 用 github 的 `Personal Access Token` 作为凭证,最佳实践,比直接用密码或者 SSH 更安全。
17 credentialsId: 'fr-github'
18 }
19 }
20
21 stage('Build Docker Image') {
22 steps {
23 script {
24 // 用 `git` 的 `HEAD commit hash` 作为镜像版本
25 IMAGE_TAG = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
26 // 贴到环境变量中以便在后续步骤中引用
27 env.IMAGE_TAG = IMAGE_TAG
28 }
29 // docker 构建镜像
30 sh '''
31 docker build -t ${ECR_REGISTRY}/${ECR_REPOSITORY}:${IMAGE_TAG} --build-arg ENVIRONMENT=staging .
32 '''
33 }
34 }
35
36 stage('Push to AWS ECR') {
37 steps {
38 // 用 AWS CLI 获取 ECR 登录密码
39 // 并推送到 ECR 仓库
40 sh '''
41 aws ecr get-login-password --region ${REGION} | docker login --username AWS --password-stdin ${ECR_REGISTRY}
42 docker push ${ECR_REGISTRY}/${ECR_REPOSITORY}:${IMAGE_TAG}
43 '''
44 }
45 }
46
47 stage('Update Deployment Config') {
48 steps {
49 // 更新另一个 GitHub 仓库中的 `deployment.yml` 文件,这个文件是 `ArgoCD` 读取的用于部署的配置文件。
50 // 将其中的 `image` 字段更新为最新的镜像版本。
51 // 然后提交并推送到 GitHub 仓库。
52 git branch: 'main', url: 'https://github.com/username/hello-world-deployment.git', credentialsId: 'fr-github'
53 withCredentials([usernamePassword(credentialsId: 'fr-github', usernameVariable: 'GIT_USERNAME', passwordVariable: 'GIT_PASSWORD')]) {
54 sh '''
55 sed -i "s|image: .*|image: ${ECR_REGISTRY}/${ECR_REPOSITORY}:${IMAGE_TAG}|g" staging/deployment.yml
56 git config user.name "Jenkins CI"
57 git config user.email "jenkins@example.com"
58 git add staging/deployment.yml
59 if git diff-index --quiet HEAD; then
60 echo "No changes to commit."
61 exit 0
62 fi
63 git commit -m "Update staging deployment to ${IMAGE_TAG}"
64 git push https://${GIT_USERNAME}:${GIT_PASSWORD}@github.com/username/hello-world-deployment.git main
65 '''
66 }
67 }
68 }
69
70 // 最后触发 `ArgoCD` 同步命令。
71 stage('Trigger ArgoCD Sync') {
72 steps {
73 sh '''
74 argocd login 13.231.229.5:32135 --username admin --password admin1234 --insecure
75 argocd app sync poc-staging
76 '''
77 }
78 }
79 }
80}
配置好保存后,一个 staging
环墶的 CI/CD
流程就配置完成了。可以尝试手动 build 一次。预期结果是应用即刻用目标分支的最新代码构建并部署。
删除 Jenkins #
有时因操作错误需重新安装 Jenkins,以下命令用于删除 Jenkins 以备不时之需。
1sudo systemctl stop jenkins
2sudo dnf remove jenkins -y
3sudo rm -rf /var/lib/jenkins
4sudo rm -rf /usr/share/jenkins
5sudo rm -rf /etc/sysconfig/jenkins