亚马逊AWS官方博客

手把手教你如何在 EKS 上轻松部署混合架构节点

Amazon EKS对由AWS Graviton支持的基于Arm的实例的支持现已可用,而且Amazon EC2 M6g、C6g 以及 R6g 实例已经在中国(宁夏、北京)区域全面上线。在AWS Graviton处理器上运行的新的通用(M6g),计算优化(C6g)和内存优化(R6g)实例比同类的当代X86产品(M5,C5和R5)高出40%的价格比。在这篇博客中,我将逐步介绍如何利用Amazon ECR的多体系架构容器镜像的新特性来部署混合架构的Amazon EKS集群,它可以使我们可以从同一镜像存储库为不同体系架构的节点部署容器变得更加简单。

在以前,在同一集群中同时具有x86和Arm架构的工作节点时,我们必须使用特定于体系架构的命名约定将此类容器镜像发布并部署到Amazon ECR,这使镜像生命周期的运维变得复杂。

容器镜像包含两个主要部分,即图层和清单。 每个容器镜像都有一层或多层文件系统内容。如下图所示:

 

 

在 Amazon ECR 中推送和拉取镜像时,您的容器引擎客户端 (例如 Docker) 将与镜像仓库进行通信以就客户端了解的清单格式以及要用于镜像的仓库达成一致。所以,当您第一次提取容器镜像以在Docker或另一个容器运行时中使用时,会发生两件事。 首先,根据指定的仓库和标签将清单拉到本地,然后使用清单从指定的层组装容器文件系统。

例如,我们可以使用 Docker inspect < image > 命令查看 Docker 开发环境中任何本地镜像的清单。正如您所看到的,镜像清单清楚地指定了诸如体系架构和操作系统之类的平台特性。那么,如何轻松地跨不同的平台架构部署容器呢?

在之前,当将镜像发布到 Amazon ECR中的存储库时,必须在 image 标记中指定这些特性。或者,您可以将特定于平台的镜像存储在它们自己的存储库中,这些镜像是由同一个源构建的。然后需要通过显式引用获取适合您的平台架构的正确版本的镜像,例如使用ARM版本是通过 {aws-account-id}.dkr.ecr.{aws-region}.amazonaws.com/my-image-linux-arm64:2.7 而不是 {aws-account-id}.dkr.ecr.{aws-region}.amazonaws.com/my-image:2.7 这是非常不方便的,并且需要在整个开发和部署镜像的生命周期中根据体系架构引用。

在 Amazon ECR 中引入多体系架构镜像

通过 Amazon ECR 中的多体系架构镜像支持,您现在可以轻松地构建不同的镜像,以支持来自同一源的多个体系架构或操作系统,并使用相同的清单摘要名称来引用它们。这是通过支持称为清单列表或镜像索引的镜像规范组件来实现的。

自 V2镜像清单以来,Docker 镜像清单规范中已经提供了对清单列表的支持。它也包含在Open Containers Initiativ (OCI) 镜像规范 v1中,尽管它被称为镜像索引。清单列表(或镜像索引)允许嵌套包含其他镜像清单,其中每个包含的镜像由体系架构、操作系统和其他平台属性指定。这样就可以通过仓库引用包含特定于平台架构的镜像,例如 {aws-account-id}.dkr.ecr.{aws-region}.amazonaws.com/my-image:2.7

如上图所示,负责创建容器的容器引擎根据清单列表中的值从注册表中为运行该计算环境的计算层提取正确的层。 在镜像构建过程中,只需执行少量额外步骤,即可按版本标签提取镜像,并为其运行的架构平台获取正确的镜像。这无疑可以简化Amazon ECR存储仓库管理以及CI / CD流水线。

在 Amazon ECR中使用多体系架构镜像

在这篇博客文章中,您将创建两个容器镜像,一个用于 x86 _ 64(基于64位 x86的系统) ,另一个用于 aarch64(基于64位 arm 的系统)。然后将这些镜像推送到 Amazon ECR中的存储仓库,然后创建一个清单列表,按照每个镜像的体系架构引用它们。最后,您将根据 manifest 列表名称提取一个镜像,而不需要指定正确的体系架构(x86 _ 64或者aarch64)。

在部署演示之前,您需要:

  • 一个 AWS 帐户和一个安装并配置了AWS CLI和kubectl的环境
  • 在Amazon ECR中创建一个命名为hello存储库
  • 一个 Docker 开发环境

在开始之前,为了简化命令的复制粘贴,请在您的 shell 中设置以下环境变量

$ export AWS_ACCOUNT_ID=aws-account-id

$ export AWS_REGION=aws-region

aws-account-id是你的AWS账号ID,通常是12位数字。aws-region是需要部署的区域。

部署演示

您需要在 Docker 开发环境中为两种不同的体系架构构建镜像。在本演练中,您可以使用自己的程序,或者克隆本演示中的 hello world 应用程序。

$ git clone https://github.com/jlbutler/yahw.git && cd yahw

$ make all

$ docker images hello

REPOSITORY TAG   IMAGE ID       CREATED         SIZE

hello      arm64 8d2063eddc5e   17 seconds ago  7.19MB

hello      amd64 cbfda9e83a41   27 seconds ago  7.59MB

请注意,本演练中的镜像使用特定于体系架构的标记(arm64/amd64)。这只是为了演示,最好用一个明确的版本或其他有意义的参考标记你的镜像。

现在您已经有了两个平台的镜像(arm64和amd64),请对其进行标记以引用您在Amazon ECR中的存储库。 根据需要将Docker客户端登录到ECR中。

$ aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin \

${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/hello

Login Succeeded

$ for i in amd64 arm64; do

docker tag hello:${i} ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/hello:${i}

done

运行 docker images 命令列出系统中的镜像:

$ docker images | grep hello

REPOSITORY                                                    TAG    IMAGE ID      CREATED        SIZE

{aws-account-id}.dkr.ecr.{aws-region}.amazonaws.com/hello     arm64  8d2063eddc5e  4 minutes ago  7.19MB

hello                                                         arm64  8d2063eddc5e  4 minutes ago  7.19MB

{aws-account-id}.dkr.ecr.{aws-region}.amazonaws.com/hello     amd64  cbfda9e83a41  4 minutes ago  7.59MB

hello                                                         amd64  cbfda9e83a41  4 minutes ago  7.59MB

使用 docker push 命令推送镜像:

$ for i in amd64 arm64; do

docker push ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/hello:${i}

done

使用aws ecr 命令验证您的镜像:

$ aws ecr --region ${AWS_REGION} describe-images --repository-name hello

{
    "imageDetails": [
        {
            "registryId": "aws-account-id",
            "repositoryName": "hello",
            "imageDigest":"sha256:b50bd7f7..5a0dc770",
            "imageTags": [
                "amd64"
            ],
            "imageSizeInBytes": 3954211,
            "imagePushedAt": "2020-04-24T17:24:11-04:00"
        },
        {
            "registryId": "aws-account-id",
            "repositoryName": "hello",
            "imageDigest": "sha256:2f333a8b..27fc2172",
            "imageTags": [
                "arm64"
            ],
            "imageSizeInBytes": 3702268,
            "imagePushedAt": "2020-04-24T17:24:25-04:00"
        }
    ]
}

 

此时,您可以通过其特定于架构的标签来提取这些镜像,但是在这次演示中,我们会以更简单的方式,通过创建清单列表并将其推送到Amazon ECR。

$ docker manifest create ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/hello \

${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/hello:amd64  \

${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/hello:arm64

Created manifest list {aws-account-id}.dkr.ecr.{aws-region}.amazonaws.com/hello:latest

清单在这里使用默认的最新(Latest)标记,但是为了生产目的,您应该使用更明确的版本。

在推送之前,您可以检查新创建的清单,并注意它有一个清单列表,其中包含两个不同的镜像引用,每个引用都具有映射到您的镜像的摘要以及适当的platform.architecture值。

$ docker manifest inspect ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/hello

{
    "schemaVersion": 2,
    "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
    "manifests": [
        {
            "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
            "size": 528,
            "digest": "sha256:b50bd7f7..5a0dc770",
            "platform": {
                "architecture": "amd64",
                "os": "linux"
            }
        },
        {
            "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
            "size": 528,
            "digest": "sha256:2f333a8b..27fc2172",
            "platform": {
                "architecture": "arm64",
                "os": "linux"
            }
        }
    ]
}

 

确认清单已准备就绪后,将其像推送任何镜像一样推送到Amazon ECR中的存储库中。

$ docker manifest push ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/hello

sha256:bd7a61a6ea3c366c0e58d70233900f0c761d6051da0ad8cbaa19011f873c37bc

接下来,为EKS集群创建x86节点和ARM节点:

创建集群:

$ eksctl create cluster --name eks-multi-arch --without-nodegroup --region=${AWS_REGION}

创建x86(本演示使用m5.Large)节点:

eksctl create nodegroup \

--cluster eks-multi-arch \

--region ap-northeast-1 \

--name x86-mng \

--node-type m5.large\

--nodes 1\

--nodes-min 1\

--nodes-max 3\

--managed

创建ARM(本演示使用Graviton2 m6g.large)节点:

$ eksctl create nodegroup \

--cluster eks-multi-arch \

--region ap-northeast-1 \

--name graviton-mng \

--node-type m6g.large \

--nodes 1\

--nodes-min 1\

--nodes-max 3\

--managed

如果创建时提示以下错误信息:

[✖]  to create an ARM nodegroup kube-proxy, coredns and aws-node addons should be up to date. Please use `eksctl utils update-coredns`, `eksctl utils update-kube-proxy` and `eksctl utils update-aws-node` before proceeding.

运行以下命令更新默认的扩展:

$ eksctl utils update-coredns --cluster eks-multi-arch

$ eksctl utils update-kube-proxy --cluster eks-multi-arch --approve

$ eksctl utils update-aws-node --cluster eks-multi-arch --approve

使用kubectl命令列出集群中的节点:

$ kubectl get nodes --label-columns=kubernetes.io/arch

NAME                                                STATUS   ROLES    AGE   VERSION               ARCH

ip-192-168-60-28.{aws-region}.compute.internal    Ready    <none>   76s   v1.17.12-eks-7684af   arm64

ip-192-168-87-164.{aws-region}.compute.internal   Ready    <none>   16m   v1.17.12-eks-7684af   amd64

部署hello应该程序和负载均衡:

编辑文件hello.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-deployment
spec:
  selector:
    matchLabels:
      app: hello
  replicas: 2 
  template:
    metadata:
      labels:
        app: hello
    spec:
      containers:
      - name: hello
        image: 123456781234.dkr.ecr.ap-northeast-1.amazonaws.com/hello:latest
        ports:
        - containerPort: 8080
编辑文件loadbalancer.yaml
apiVersion: v1
kind: Service
metadata:
  name: hello-service
spec:
  type: LoadBalancer
  selector:
    app: hello
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080

使用 kubectl apply 命令创建资源

$ kubectl apply -f hello.yaml

$ kubectl apply -f loadbalancer.yaml

列出刚创建的资源

$ kubectl get service

NAME            TYPE           CLUSTER-IP    EXTERNAL-IP                                                              PORT(S)        AGE

hello-service   LoadBalancer   10.100.22.0   aaa71bxxxxx-11xxxxx10.{aws-region}.elb.amazonaws.com  80:31450/TCP   38s

kubernetes      ClusterIP      10.100.0.1    <none>                                                                         443/TCP        25m

从上面的命令中,我们可以获得负载均衡的地址“aaa71bxxxxx-11xxxxx10.{aws-region}.elb.amazonaws.com”,我们可以通过访问这个地址来验证多体系架构的节点,例如连续运行多次curl获得架构信息:

$ curl aaa71bxxxxx-11xxxxx10.{aws-region}.elb.amazonaws.com/hello

{“arch”:”x86_64“,”message”:”Hello, there!”,”os”:”Linux 4.14.209-160.335.amzn2.x86_64″}

$ curl aaa71bxxxxx-11xxxxx10.{aws-region}.elb.amazonaws.com/hello

{“arch”:”x86_64“,”message”:”Hello, there!”,”os”:”Linux 4.14.209-160.335.amzn2.x86_64″}

$ curl aaa71bxxxxx-11xxxxx10.{aws-region}.elb.amazonaws.com/hello

{“arch”:”aarch64“,”message”:”Hello, there!”,”os”:”Linux 4.14.209-160.335.amzn2.aarch64″}

$ curl aaa71bxxxxx-11xxxxx10.{aws-region}.elb.amazonaws.com/hello

{“arch”:”aarch64“,”message”:”Hello, there!”,”os”:”Linux 4.14.209-160.335.amzn2.aarch64″}

总结

借助多体系架构镜像支持,您的构建和部署可以引用单个镜像名称和特定于版本的标记,而不再需要引用操作系统,体系架构或其他平台详细信息。这大大简化了容器流水线,从而为发布的容器镜像启用了更简单的镜像命名约定。

更多内容

云上 ARM 实例应用优化之我见

https://aws.amazon.com/cn/blogs/china/optimization-of-arm-example-application/

Introducing multi-architecture container images for Amazon ECR

https://aws.amazon.com/blogs/containers/introducing-multi-architecture-container-images-for-amazon-ecr/

Amazon EKS on AWS Graviton2 generally available: considerations on multi-architecture apps

https://aws.amazon.com/blogs/containers/eks-on-graviton-generally-available/

本篇作者

吴金福

AWS 混合云方案架构师,负责基于AWS的混合云方案架构的咨询和设计。在加入AWS之前,就职于大型集团企业。负责私有云数据中心的方案设计和建设,在数据中心基础设施、虚拟化、高性能计算和混合云等领域有着多年的经验积累。