亚马逊AWS官方博客

使用 Amazon CodePipeline 来自动化部署到 Amazon EKS 集群

随着技术的快速发展,很多现代化的应用都会以容器的形式部署在 Kubernetes 集群上。为了更高效、安全地进行应用部署,很多开发团队都有自动化部署和构造持续集成流水线的需要。

在这篇文章中,我们从头开始来演示如何使用 Amazon CodePipeline 来构建一条流水线,在提交代码的时候自动触发编译、构建、推送镜像以及部署到 Kubernetes 集群一系列的工作。

我们的最终目标是形成如下一个自动化部署流水线。

文章分为如下几个部分:

  1. 准备工作,安装必须的工具
  2. 本地测试,即在本地运行应用,再封装成 Docker 镜像来运行
  3. 创建 EKS 集群,即创建托管的 Kubernetes 集群并做基础配置
  4. 手动部署,即在本地把容器应用部署到 Kubernetes 集群
  5. 自动部署,使用 CodePipeline 在代码更新时自动部署

1. 准备工作

在开始之前,我们必须做好如下几件准备工作。

  • 安装 Docker,用于打包和推送容器镜像
  • 安装 eksctl,这是用来构建 Amazon EKS 集群的工具。
  • 安装 kubectl,这是用来对 K8s 集群进行操作(比如部署应用)的工具。
  • 安装 AWS CLI(命令行工具),因为 kubectl 依赖它把 K8s 的用户组和权限对应到 AWS 用户,让我们可以直接以 AWS 用户的身份对 K8s 集群进行操作。
  • 安装 Java 11,在我们本地测试的时候会用到。
  • 下载 Spring Boot 样板项目,用这个最基础简单的 Java 项目当做我们的应用。

接下来我们介绍下如何这些执行这些准备工作。

1-1. 安装 Docker

我们可以参照 Docker 官方安装页面进行安装。

以 CentOS 为例,我们可以用如下命令启用 Docker 官方应用仓库,然后安装并启动 Docker 服务。

# 添加 Docker 应用仓库

sudo yum install -y yum-utils

sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo

# 安装 Docker

sudo yum install docker-ce docker-ce-cli containerd.io

# 启动 Docker 服务

sudo systemctl start docker

安装完成后,可以通过如下命令进行验证。

docker version

1-2. 安装 eksctl

本次我们略过 Kubernetes 从零开始安装的问题,而是基于 EKS 来执行。我们可以在控制台操作,但 EKS 也提供一个命令行工具 eksctl 帮助我们启动和管理 EKS 集群,它更加快速、准确,所以,本次我们使用这个工具。

通常来说,如果使用 Linux,你可以使用如下命令下载 eksctl。

curl --silent --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp
sudo mv /tmp/eksctl /usr/local/bin

其他系统的安装方式可以参考官方页面

下载安装完成后,可以执行下列命令输出 eksctl 版本。成功输出则代表安装成功。

eksctl version

1-3. 安装 kubectl

针对 Kubernetes 的管理分两个部分。一个部分是对集群本身和底层的资源进行管理,这个部分我们使用 eksctl,另一个部分则是对集群内资源的管理,比如部署应用、查看 Pods 等,这个使用 kubectl。

使用 Linux 并且具备 root 权限时,可以用如下命令进行安装。

cd /tmp
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl

其他安装方式可以参考官方页面

下载安装完成后,可以执行下列命令输出 kubectl 版本。成功输出则代表安装成功。其中 –client 参数代表只输出客户端的版本,因为目前我们还没创建 Kubernetes 集群,无法获知服务端版本。

kubectl version --client

1-4. 安装 AWS CLI

在我们的部署中,有两个部分需要跟亚马逊云科技的服务打交道。第一个是使用 eksctl 创建和管理 EKS 集群时,这个很容易理解。另一个则是通过 kubectl 向 EKS 托管集群发送指令时,也会需要用到亚马逊云科技的身份。

这是因为 EKS 提供了身份映射的功能,可以把 Kubernetes 的身份映射到亚马逊云科技的 IAM 身份上,这样就可以直接用 IAM 身份来向 Kubernetes 发 API 请求。

同行,使用 Linux 和有 root 权限,并且使用 x86 架构时,可以使用以下命令进行安装。

curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install

其他下载方式可以参考官方页面

安装完成后,我们需要在亚马逊云科技控制台,找到 IAM 下的「用户」,点击进入自己的用户,然后找到「安全证书」标签,在页面中的「访问密钥」,并点击「创建访问密钥」。此时会弹出一个窗口包含「访问密钥 ID」以及「私有访问密钥」,注意复制保存下来。

接下来,我们使用以下命令把用户密钥录入到本地,就可以使用这个用户身份来调用亚马逊云科技的 API。

aws configure

这个命令会分别提示:

  • 访问密钥 ID(AWS Access Key ID)
  • 私有访问密钥(AWS Secret Access Key ID)
  • 区域(本文使用宁夏区域,输入 cn-northwest-1)
  • 默认输出格式(直接回车保持不变即可)

配置完成后,可以输入如下命令确认当前调用 API 的身份。

aws sts get-caller-identity

1-5. 安装 Java 11

我们本次使用 Spring Boot 的样板应用,它基于 Java 11,所以我们需要安装 Java 11。

部分操作系统已经具备 Java 11。大部分操作系统的官方软件仓库包含 Java,可以直接通过包管理器安装。比如,在 RHEL 或 CentOS 上,可以使用 sudo yum install java 来安装。

安装完成后,可以用下列命令确认版本。

java --version

正常应该会输出 java 11.0.2 2019-01-15 LTS 之类,确认为 Java 11 版本。

1-6. 下载 Spring Boot 样板项目

接下来我们下载 Spring Boot 样板项目。官方下载地址是 start.spring.io。下载时选择 Maven 项目、Spring Boot 版本为 2.5.10,Java 版本为 11。

我们也可以使用下列命令直接下载并解压。注意:此链接的接口可能被修改,如果报错,可到官方网址手动下载。

curl https://start.spring.io/starter.zip -d bootVersion=2.5.10 -d javaVersion=11 -d type=maven-project -d dependencies=web -O
unzip starter.zip

2. 本地测试

准备就绪之后,我们来做本地测试。

2-1. 修改和编译

我们先修改这个项目,把它做成一个常驻的 Web 服务。注意:此项请在项目根目录执行。

# 删除原来的项目

rm -rf src/test src/main/java/com

# 加入 com/hello/Application.java

mkdir -p src/main/java/hello
cat <<'EOF' > src/main/java/hello/Application.java
package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class Application {

  @RequestMapping("/")
  public String home() {
    return "Hello World.";
  }

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }

}
EOF

使用项目中提供的 Maven Wrapper 对这个项目进行编译。注:Maven Wrapper 指的是把 Maven 工具的某个指定版本打包在源码内提供,避免用户在使用源码时还要去寻找对应的 Maven 版本。

./mvnw package

编译完成后,开始做本地测试。

2-2. 进程式应用

我们最传统的应用运行方式,是直接执行一个应用,把它做成一个常驻的服务。接下来我们直接执行这个应用。

java -jar target/demo-0.0.1-SNAPSHOT.jar

这个应用默认在 8080 端口输出 Hello World.。我们可以通过开新窗口,并使用如下命令来测试。

curl localhost:8080

或者用浏览器进行测试。

2-3. 容器化应用

确认应用可以跑起来后,我们就可以把它容器化了。为简便起见,这里我们只使用单个容器来封装这个应用。

我们先编写 Dockerfile 文件。

# 使用 OpenJDK 11 的官方瘦身版镜像

FROM openjdk:11-jre-slim-buster

# Spring Boot 添加用户和组

RUN addgroup --group spring && adduser --system --ingroup spring spring

# 使用前面创建的用户

USER spring:spring

# 复制编译好的应用到目录

ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar

# 把应用设置为容器入口

ENTRYPOINT ["java","-jar","/app.jar"]

接下来,我们创建这个镜像。

docker build . -t test-app

创建完成后,使用如下命令应该能看到名为 test-app 的本地镜像。

docker images

使用如下命令来运行我们的容器,并把本机的 8080 端口映射到容器的 8080 端口。

docker run -p 8080:8080 -d test-app

Docker 会在后台运行容器,并返回容器的 ID,记录下这个 ID。接下来,我们可以按照之前的方式验证我们的应用。

curl localhost:8080

注意容器启动可能会需要几秒时间,所以如果当时无法成功测试,可以稍等几秒再试。我们也可以通过如下命令看到正在运行的容器。

docker ps

测试完成后,我们用以下命令终止容器,把 <container-id> 换成前面记录的 ID。

docker stop <container-id>

接下来我们分别用手动和自动的方式把这个应用部署到 Kubernetes 集群。

3. 创建 EKS 集群

在这个步骤,我们会创建 EKS 集群和 Amazon ECR 镜像仓库。

3-1. 创建集群

我们先使用 eksctl 创建一个集群。

eksctl create cluster --name=test-eks --node-type t3.medium --managed --alb-ingress-access --region=cn-northwest-1

其中节点的实例类型是 t3.medium,使用托管式节点(–managed),并且给节点角色添加了 ALB(应用负载均衡器)的访问权限(–alb-ingress-access),方便后续使用 AWS LB Ingress 来做 Kubernetes 入口。

这个步骤耗时较长,可能需要 10-15 分钟。

创建完成后,你可能会看到如下错误。

20xx-xx-xx xx:xx:xx [✖]  unable to use kubectl with the EKS cluster (check 'kubectl version'): Unable to connect to the server: getting credentials: exec plugin is configured to use API version client.authentication.k8s.io/v1alpha1, plugin returned version client.authentication.k8s.io/v1beta1

这是因为我们的 kubectl 目前还没有对接到我们新创建的集群。我们还需要运行如下命令。

aws eks update-kubeconfig --region cn-northwest-1 --name test-eks

这个命令会更新我们本地的 kubectl 配置,让它使用我们的 IAM 身份来访问集群。使用如下命令列出现在集群中的节点来进行测试。

kubectl get nodes

3-2. 部署 AWS LB Ingress

成功列出集群节点之后,我们来创建 AWS LB Ingress 自定义资源。这个自定义资源会在我们申请 Ingress 的时候,帮助我们创建 ALB 并把这个 ALB 作为入口。

我们在 EKS 中创建的 AWS LB Ingress 将使用服务账号(Service Account)来执行一系列资源创建的操作,而服务账号的身份会映射 IAM 上,从而可以用 IAM 的身份来操作亚马逊云科技上的资源。

要做到这一点,首先我们需要创建一个 IAM OIDC 身份提供商,并且把这个 OIDC 身份提供商对接到我们的 EKS 集群。

eksctl utils associate-iam-oidc-provider \
    --region cn-northwest-1 \
    --cluster test-eks \
    --approve

接下来,下载新角色对应的权限描述。

curl -o iam-policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.4.0/docs/install/iam_policy.json

因为这个权限描述对应的是亚马逊云科技的 Global 区域,其使用的资源标识符分区与宁夏区域不同,所以我们需要把其中的 arn:aws 换成 arn:aws-cn。

sed -i -e 's|arn:aws|arn:aws-cn|' iam-policy.json

然后,我们用这个权限描述创建一条 IAM 权限策略。注意:此处的权限可以操作所有安全组,供测试使用;实际生产使用时,我们应该将权限限定在某个 VPC 或者带某个标签的安全组上,参考官方文档

aws iam create-policy \
--policy-name AWSLoadBalancerControllerIAMPolicy \
--policy-document file://iam-policy.json

接下来,使用这个策略来创建一个 IAM 角色,创建对应的 Kubernetes 服务账号,并做关联。下面的 eksctl 命令帮助我们一次执行这三个步骤,注意把 <account-id> 换成你的亚马逊云科技账号 ID。

eksctl create iamserviceaccount \
--cluster=test-eks \
--namespace=kube-system \
--name=aws-load-balancer-controller \
--attach-policy-arn=arn:aws-cn:iam::<account-id>:policy/AWSLoadBalancerControllerIAMPolicy \
--override-existing-serviceaccounts \
--region cn-northwest-1 \
--approve

接下来是部署我们的 AWS LB Ingress 资源。

先部署它依赖的 cert-manager。

kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v1.5.3/cert-manager.yaml

然后升级它的自定义资源定义(Custom Resource Definition)配置。

kubectl apply -k "github.com/aws/eks-charts/stable/aws-load-balancer-controller//crds?ref=master"

然后下载 AWS LB Ingress 的配置文件。

wget https://github.com/kubernetes-sigs/aws-load-balancer-controller/releases/download/v2.4.0/v2_4_0_full.yaml

打开这个文件,找到 –cluster-name=your-cluster-name 字样,然后将 your-cluster-name 改为 test-eks(我们本次的 EKS 集群的名字)。

然后执行下列命令把这个配置部署到 Kubernetes 集群。

kubectl apply -f v2_4_0_full.yaml

3-3. 创建 ECR 镜像仓库

接下来,我们需要创建一个 ECR 镜像仓库。

aws ecr create-repository --repository-name test-app

如果不想用命令行,这个步骤也可以在亚马逊云科技 Web 控制台完成,没有特别的配置,只需要输入名字即可。这时候我们本地的 Docker 还不知道这个仓库的存在,所以,我们需要登录到这个仓库。

ECR 提供了一个简便方式帮助登录,命令如下。注意需要把 <account-id> 换成你的账号 ID。

aws ecr get-login-password | docker login --username AWS --password-stdin <account-id>.dkr---ecr---cn-northwest-1.amazonaws.com.rproxy.goskope.com.cn

登录成功,我们的准备工作就完了,接下来就是手动部署。

4. 手动部署

要手动部署一个容器服务,需要如下几个步骤。

  • 容器推送到 ECR 仓库
  • 创建部署配置文件
  • 应用部署配置文件

4-1. 推送到 ECR 仓库

用如下命令给本地的镜像打 ECR 标签并推送到 ECR 仓库。注意把 <account-id> 修改成你的账号 ID。

docker tag test-app:latest <account-id>.dkr---ecr---cn-northwest-1.amazonaws.com.rproxy.goskope.com.cn/test-app:v1

docker push <account-id>.dkr---ecr---cn-northwest-1.amazonaws.com.rproxy.goskope.com.cn/test-app:v1

4-2. 手动部署

接下来,我们把如下内容复制成 test-app.yml。注意替换 <account-id>。

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-app
  labels:
    app: test-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: test-app
  template:
    metadata:
      labels:
        app: test-app
    spec:
      containers:
      - name: springboot
        image: <account-id>.dkr---ecr---cn-northwest-1.amazonaws.com.rproxy.goskope.com.cn/test-app:v1
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: "service-test-app-clusterip"
spec:
  selector:
    app: test-app
  type: ClusterIP
  ports:
  - protocol: TCP
    port: 8080
    targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: "alb-ingress"
  namespace: "default"
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 8888}]'
  labels:
    app: test-app
spec:
  rules:
    - http:
        paths:
        - path: /
          pathType: Prefix
          backend:
            service:
              name: "service-test-app-clusterip"
              port:
                number: 8080

然后,执行如下命令来部署这个应用。

kubectl apply -f test-app.yml

使用如下命令我们可以看到负载均衡器的外部地址。

kubectl get ingress

这里需要注意的是,虽然部署命令会马上返回「已创建」(created),但实际上负载均衡器需要一些时间才能创建。如果地址无法访问,可以在亚马逊云科技 Web 控制台 EC2 服务下的「负载均衡器」界面查看负载均衡器的状态,确认处于活跃状态再进行测试。

另外还需要注意的是为了测试,我们使用了 8888 端口,所以 Ingress 的访问地址会类似于:

http://k8s-default-albingre-xxxxxxxx---cn-northwest-1---elb.amazonaws.com.rproxy.goskope.com.cn:8888

这样,应用就手动部署成功了。

注:如果在这个过程中遇到问题,我们可以通过 kubectl get events 来查看 Kubernetes 的资源事件,帮助排错。

4-3. 部署新版

接下来,我们要修改代码,然后尝试覆盖原来的部署。

先打开 src/main/java/hello/Application.java,然后把里面的 Hello World. 改成 Hello New World. 并保存。

接下来,重新编译这个应用,打成镜像,并推送到 ECR 仓库。注意修改 <account-id>。

./mvnw package
docker build . -t <account-id>.dkr---ecr---cn-northwest-1.amazonaws.com.rproxy.goskope.com.cn/test-app:v2
docker push <account-id>.dkr---ecr---cn-northwest-1.amazonaws.com.rproxy.goskope.com.cn/test-app:v2

然后编辑我们的 test-app.yml,把 <account-id>.dkr---ecr---cn-northwest-1.amazonaws.com.rproxy.goskope.com.cn/test-app:v1 末尾的 v1 改成 v2。重新执行部署命令。

kubectl apply -f test-app.yml

稍等片刻,刷新前面的 Ingress 网址,就能看到浏览器中的 Hello World. 变成了 Hello New World.。

5. 自动部署

接下来,我们使用 Amazon CodePipeline、Amazon CodeCommit 和 Amazon CodeBuild 来自动部署这个应用,整体流程如下。

  • 代码提交到 CodeCommit 代码仓库
  • CodePipeline 负责监听 CodeCommit 的代码仓库,有新的代码提交时启动流水线。
  • 流水线启动后,CodePipeline 将代码下载并启动 CodeBuild 的编译项目。
  • CodeBuild 从启动参数中获得代码地址,自动下载后,按照 buildspec 的指令按顺序执行编译动作,打包成镜像,并上传到 ECR 镜像仓库,再应用 K8s 部署任务。

5-1. 使用代码仓库

在开始之前,我们需要先创建 CodeCommit 代码仓库。可以通过如下命令,或者在 Web 控制台创建。

aws codecommit create-repository –repository-name test-app

创建完成后,记录下它的地址。但此时还没有用户。

我们需要到 IAM 服务找到自己的用户,然后找到「安全证书」,下拉找到「针对 Amazon CodeCommit 的 HTTPS Git 凭证」,点击生成,暂时不要关闭这个窗口。

接下来,我们使用 Git 客户端克隆这个仓库到本地。把 <repo-uri> 换成我们的 CodeCommit 仓库地址,使用 HTTPS 地址。

git clone <repo-uri>

然后,它会提示用户名和密码。此时就可以复制使用我们前面生成的用户名和密码。克隆成功后,本地会有一个 test-app 文件夹,我们把之前的代码目录复制进去。复制完成后,它应该包含如下内容(使用 ls -la 查看)。

  • .mvn
  • .gitignore
  • src/
  • xml
  • mvnw
  • dockerfile
  • test-app.yml

然后,我们使用下列命令把这些推送到 CodeCommit 仓库。

git add .
git commit -m "initial commit"
git push

重点注意:原来的 Spring Boot 目录下包含了 .mvn 等隐藏文件夹和文件,需要使用 cp -r 等方式把隐藏的文件夹也复制过来否则无法正常编译。

5-2. 创建 CodeBuild 项目

CodeBuild 提供一个即用即抛的容器环境来为我们编译代码。除了编译代码之外,我们可以把它当成是一个通用的计算环境,执行任意的命令,包括构建和推送容器镜像和自动化测试等。

要使用 CodeBuild,我们需要准备一个 buildspec 文件。这个文件是 YAML 格式,记载需要执行的动作等。

把下面的内容保存成 buildspec.yml,置于源代码根目录。

version: 0.2

phases:
  install:
    commands:
      - curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
      - aws --version
      - aws eks update-kubeconfig --region $AWS_REGION --name $EKS_CLUSTER_NAME
      - aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com.rproxy.goskope.com.cn
  build:
    commands:
      - ./mvnw package
      - docker build -t $IMAGE_REPO_NAME:$CODEBUILD_START_TIME .
      - docker tag $IMAGE_REPO_NAME:$CODEBUILD_START_TIME $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com.rproxy.goskope.com.cn/$IMAGE_REPO_NAME:$CODEBUILD_START_TIME
  post_build:
    commands:
      - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com.rproxy.goskope.com.cn/$IMAGE_REPO_NAME:$CODEBUILD_START_TIME
      - sed -i -e "s/\($AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com.rproxy.goskope.com.cn\/[^:]*:\)[a-z0-9_-]*/\1$CODEBUILD_START_TIME/g" test-app.yml
      - kubectl apply -f test-app.yml

可以看出,这个构建一共分三个阶段:

  • install 阶段,安装 kubectl 并配置,登录 ECR;我们不需要再安装 AWS CLI 和 Docker,因为我们选择的环境中会带有这两个工具
  • build 阶段,编译代码,构建容器镜像,给镜像打标签
  • post_build 阶段,把镜像推送到 ECR 镜像仓库,修改部署配置中的镜像为最新的版本,然后应用新的部署配置

接下来,我们要创建 CodeBuild 项目。打开 CodeBuild 服务,创建项目,输入名字 test-app-build。

  • 源选择 CodeCommit,并在下拉框中选择 test-app,分支选择 master
  • 镜像选择托管,Ubuntu,Standard 5.0,Linux,并勾选「特权」
  • 服务角色选择新服务角色

在环境下,点开「其他配置」,找到环境变量,添加如下几个环境变量。

  • IMAGE_REPO_NAME = test-app
  • AWS_ACCOUNT_ID = 你的账号 ID
  • EKS_CLUSTER_NAME = test-eks

环境变量不是必须添加,只是我们的 buildspec.yml 中有用到,添加后大家可以修改这些环境变量,即可编译其他 CodeCommit 代码仓库集群的代码到不同的集群。

其余默认即可。

这里需要说明一下的是「特权」,指的是 Docker 的「Privilege 模式」。因为默认情况下我们不能在 Docker 中再运行一个 Docker 的后台服务,从而无法在容器中构建镜像,而 Privilege 模式则移除了这个限制。因为我们要构建镜像,所以需要开启特权模式。

5-3. 修改 CodeBuild 角色

我们还需手动到 IAM 里创建一个 CodeBuild 服务使用的角色。

原本 CodeBuild 在创建项目时已经自动创建了一个角色,但是因为 eksctl 目前的一些限制,导致它无法正常识别名字中带 service-role/ 前缀的 IAM 角色,而 CodeBuild 自动创建的角色正好带了这个前缀。这使得我们无法在 CodeBuild 容器中正常使用 IAM 角色调用 Kubernetes API。所以,我们只能自己创建一个名字中不带前缀的角色。

打开 IAM 服务,找到刚刚自动创建的 codebuild-test-app-build-service-role 角色,然后将其名下的 CodeBuildBasePolicy-test-app-build-cn-northwest-1 策略移除。再创建一个新的角色使用案例选择 CodeBuild,命名为 codebuild-test-app-build-role,把这个前面移除的策略附加到这个新的角色。

我们还需给这个角色登录 ECR,上传 ECR 镜像以及登录 EKS 集群的权限。还是在 IAM 角色详情界面,选择创建内联策略,并点击 JSON 标签,输入如下内容并保存。注意修改 <account-id>。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "v0",
            "Effect": "Allow",
            "Action": "ecr:GetAuthorizationToken",
            "Resource": "*"
        },
        {
            "Sid": "v1",
            "Effect": "Allow",
            "Action": [
                "ecr:CompleteLayerUpload",
                "ecr:UploadLayerPart",
                "ecr:InitiateLayerUpload",
                "ecr:BatchCheckLayerAvailability",
                "ecr:PutImage"
            ],
            "Resource": [
                "arn:aws-cn:ecr:cn-northwest-1:<account-id>:repository/test-app"
            ]
        },
        {
            "Sid": "v2",
            "Effect": "Allow",
            "Action": [
                "eks:UpdateClusterConfig",
                "eks:DescribeCluster"
            ],
            "Resource": "arn:aws-cn:eks:cn-northwest-1:<account-id>:cluster/test-eks"
        }
    ]
}

再回到 CodeBuild 页面,编辑环境,并选择我们自己创建的 codebuild-test-app-build-role 并保存。

5-4. 添加 CodeBuild 服务账号

此时我们仍无法通过 CodeBuild 编译,因为 CodeBuild 使用的是自己的 IAM 服务角色,而这个角色我们并没有在 Kubernetes 集群中做过映射。我们还需要为它创建一个 Kubernetes 服务账号(Service Account),并映射到 codebuild-test-app-build-role 这个 IAM 服务角色上。

读者可能会好奇,我们在本地使用 kubectl 的时候,直接使用 update-kubeconfig 就可以了,也没有单独创建映射,为什么 CodeBuild 就需要单独配置?这是因为 EKS 创建的 Kubernetes 集群有一个「隐藏设定」,那就是「集群创建者永远拥有该集群的最高权限」,并且这个权限是隐性的,不会体现在任何地方,也无法回收。这也意味着在生产环境中,我们最好使用一个单独的账号来创建集群,并且严格管控这个账号的使用。

接下来我们创建映射关系。EKS 托管的 Kubernetes 集群已经做了对 IAM 账号和集群的支持。我们执行如下命令,打开映射关系。

kubectl edit -n kube-system configmap/aws-auth

在 mapRoles 下增加如下记录。注意修改 <codebuild-role-arn>。

- groups:
        - system:masters
        rolearn: <codebuild-role-arn>
        username: codebuild-role

重点注意:为简化演示,此处我们将这个角色分到了 system:masters 组,也就是对此 Kubernetes 集群有完全访问权限,但在实际使用中,我们建议创建一个单独的组并限定其权限。

接下来我们验证手动触发 CodeBuild 编译、构建和部署。首先,修改代码中返回的字符串。

然后本地 Git 提交并推送到 CodeCommit 代码仓库。

git add .
git commit -m "change return string"
git push

最后,我们到 CodeBuild 的控制台,打开 test-app-build 项目,点击「开始构建」。这个构建会花数分钟时间,我们可以在下方窗口看到构建过程执行的命令和输出的日志。

编译和部署成功后,稍等片刻,刷新之前的网址,可以看到输出已经变化。

5-5. 使用 CodePipeline 自动触发编译部署

现在,提交代码后,我们是手动触发构建。接下来,我们把最后的手动触发步骤也自动化,使用 CodePipeline,在代码仓库接收到新代码的时候,自动下载代码,传输给 CodeBuild,并自动开始编译和部署的全流程。

打开 CodePipeline 服务,创建流水线,命名为 test-app-pipeline。

  • 「源」选择 CodeCommit,仓库是 test-app,分支是 master
  • 「构建」选择 CodeBuild,项目是 test-app-build
  • 「部署」选择下方的「跳过部署阶段」,因为我们在 CodeBuild 中直接就部署了

我们在 CodePipeline 和 CodeBuild 中都配置了「源」,如果构建由 CodePipeline 触发,那么 CodeBuild 的源配置会被 CodePipeline 的覆盖。

接下来我们只需要再通过 Git 提交一份新代码,就能触发部署。

在部署成功后,刷新浏览器,就能看到最新的应用。

6. 总结

这篇文章简单介绍了从本地应用,到容器包装,到手动部署到 Kubernetes 集群,再到使用 CodeCommit 和 CodeBuild 半自动部署,最后到 CodePipeline 全自动部署的完整流程。

基于这个流程,用户可以有很多扩展空间,比如:

  • 在 CodePipeline 中增加手动审批环节
  • 在 CodeBuild 中执行单元测试和集成测试
  • 在编译出错或者完成时发送通知
  • 蓝绿部署
  • 集群的代码化和快速复制
  • 更细粒度的权限控制

这些都有待大家的探索。

作为托管服务,Code 系列大大减轻了持续集成的上手难度。大部分的操作都可以使用 Web 界面和命令行两种方式,也可以直接写成配置代码,方便在不同区域间复制。

在敏捷开发的时代,持续集成是保障软件质量的重要工具。希望这篇文章能帮助读者快速建立起自己的第一条研发部署流水线。

本篇作者

张玳

AWS 解决方案架构师。十余年企业软件研发、设计和咨询经验,专注企业业务与 AWS 服务的有机结合。译有《软件之道》《精益创业实战》《精益设计》《互联网思维的企业》,著有《体验设计白书》等书籍。