亚马逊AWS官方博客

让容器仓库飞起来:Amazon ECR 多区域分发与统一域名就近接入

容器仓库概览

容器技术帮助企业将 DevOps 落地实现,同时其标准化、规范化的工具与框架,极大地提升了企业 IT 应用开发、迭代及运维效率。伴随着云技术、云原生的快速发展,企业 IT 应用架构已经开始从传统基础设施为中心转向以应用为中心,容器技术也越来越成为企业 IT 应用的默认选择。

容器镜像仓库,是容器技术的关键一环。镜像仓库负责存储镜像文件、管理访问权限、提供镜像上传与下载服务,并作为 DevOps 流程的承上(持续交付)启下(持续部署)环节,将应用的编译打包与容器编排/部署平台(如 Docker 和 Kubernetes)串接起来,充当系统之间映像共享的中介。

一般来说,容器镜像仓库可以分为公有仓库与私有仓库。最有名的镜像仓库是 Docker 提供的 Docker Hub 服务,它同时提供公有仓库与私有仓库服务。同时,也有如 Quay 以及 Harbor 之类的开源镜像仓库方案。

Amazon ECR 简介

在云端,亚马逊云科技提供了Amazon Elastic Container Registry (ECR) ,一种完全托管的 Docker 容器仓库服务,同时提供了公有仓库与私有仓库服务,开发人员可以轻松、简单的使用它存储、管理和部署容器镜像,简化生产工作流开发。

使用 Amazon ECR,您无需搭建、运维自己的镜像仓库,也无需担心底层基础设施容量。Amazon ECR 将您的镜像存储于高可用且弹性扩展的架构中,为企业 IT 应用程序提供可靠镜像仓库。同时,与 Amazon Identity and Access Management (IAM) 集成,您可以对每个存储库进行资源级控制。Amazon ECR 无需预付费,您只需为存储库中存储的惊喜存储空间及传输到 Internet 的流量付费。

容器仓库在多区域 DevOps 中的痛点

一般的 CI/CD 中,编译打包与部署环境往往位于同一区域,各个系统之间的网络带宽充足、网络时延极低,镜像仓库的镜像上传/下载一般不会成为 DevOps 流程瓶颈。随着企业 IT 应用的成长,企业 IT 应用往往面临高可用、多区域、全球化的需求,此时,网络带宽与时延将极大影响镜像仓库的上传/下载,如何让多个区域之间快速同步、共享镜像,同时保持持续部署代码、配置的统一,成为 DevOps 中无法绕开的话题。

本文介绍了一种通过 Amazon ECR,实现容器镜像全球分发,并实现容器仓库域名统一,镜像就近拉取的方案,帮助客户落地多区域 Devops。方案总体架构图如下:

  • 持续交付的新镜像,推送到主 Amazon ECR 后,通过 Amazon ECR 跨 Region 同步实现多区域分发。
  • Region 内部,通过 ELB 与 Nginx 反向代理,对 ECR 访问进行封装,与 Amazon ECR 默认域名解耦,实现自定义域名访问。
  • 用户侧,通过 Amazon Route 53 实现就近解析到对应的 ELB,最终通过反向代理从 Amazon ECR 拉取到相应镜像。

Amazon ECR 的全球分发

镜像交付上传

在构建打包生成镜像后,我们需要将生成的镜像上传到 Amazon ECR。上传过程一般来说需要自动化,所以我们这里使用命令行方式。我们也可以根据持续交付的具体实现,使用 SDK 程序化方式上传。

1. 登录目标镜像仓库。于其他镜像仓库一样,我们需要先登录进镜像仓库:

# 获取登录密码
aws ecr get-login-password
# 登录 ECR 服务。注意将<111111111111>替换为自己的 Amazon Web Services account id, <region> 替换为 ECR 服务所在 region
sudo docker login -u AWS <111111111111>.dkr.ecr.<region>.amazonaws.com

# 或者,一步操作
aws ecr get-login-password | sudo docker login -u AWS --password-stdin <111111111111>.dkr.ecr.<region>.amazonaws.com

get-login-password 登录默认有效期是 12 小时。所以,我们需要在打包服务器上定期做登录操作,确保保持登录状态。

2. 通过 docker 命令行,上传生成的镜像到 Amazon ECR:

# 创建目标仓库,避免仓库不存在
aws ecr create-repository  --repository-name <some-prefix-as-your-wish/repo-name>

# 为目标镜像添加 tag
sudo docker tag <image:tag or image id> <111111111111>.dkr.ecr.<region>.amazonaws.com/<some-prefix-as-your-wish/repo-name:tag>

# 将镜像上传到 ECR
sudo docker push <111111111111>.dkr.ecr.<region>.amazonaws.com/<some-prefix-as-your-wish/repo-name:tag>

做完该步操作后,镜像将上传到 ECR,供后续使用。

镜像跨区域复制

在 Amazon ECR 控制台,可以快速配置同步。参考下图:

当然,也可以通过命令行进行操作:

# 参考 https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ecr/put-replication-configuration.html
aws ecr put-replication-configuration --replication-configuration file://replication-settings.json      --region <taget-region>

配置完成后,当有新的镜像推送到 ECR 时,该镜像将自动同步到目标账号/区域。

其他注意事项

  • ECR 镜像同步只会同步增量容器镜像,已经存在的存量镜像将不会同步。 所以建议提前设置好镜像同步。存量镜像可以通过编写脚本批量 pull 再 push 到目标的方式镜像同步。
  • ECR 镜像同步功能不会同步删除操作。这意味着,当需要删除镜像的时候,需要自行进行删除。
  • ECR 镜像同步支持跨账号、跨区域同步。跨账号同步请提前配置配置好相应权限。具体可以参考 Amazon ECR 跨账号同步文档

Amazon ECR 的统一域名

现有的容器调度系统,如 Kubernetes,我们通过 yaml 文件定义容器镜像。为了适配多区域场景,我们往往需要在 yaml 模版中,将容器镜像定义为变量,让后通过一定的编程实现填入不同的 ECR 容器仓库域名。这意味着增加了持续交付的实现难度与后期运维的复杂度,所以,真实场景中,我们需要通过统一域名,统一 yaml 中 image 字段,确保多区域场景的统一管理,同时,这个统一域名最好也是企业自主可控的域名。

现阶段,Amazon ECR 每个 region 的 ECR 域名各不相同,域名包含 account id 与 region 信息,不能实现 account/region 无关。同时,也暂不支持自定义域名。同时,Amazon ECR 不支持 insecure 仓库(即不支持 HTTP),其证书为 Amazon 私有证书(证书绑定域名 *.dkr.ecr.<region>.amazonaws.com),我们也无法通 CNAME 方式实现自行绑定。所以,我们需要反向代理帮助我们来实现统一域名入口。

基于 Nginx 的反向代理方案

Nginx 反向代理主要是配置 Nginx,关于 Nginx 安装我们不再赘述。配置 Nginx 之前,我们需要获取 Amazon ECR 的验证信息:

#  参考 https://docs.aws.amazon.com/AmazonECR/latest/userguide/registry_auth.html
aws ecr get-authorization-token | jq -r ".authorizationData[].authorizationToken" 

保存获取到的 token 信息,后面配置 nginx 会用到。

关于 Nginx 的配置,主要是以下几点:

  • upstream 配置: 反向代理到哪儿。填 Amazon ECR 的 URL
  • 更改每个 request 的 Host 字段为 Amazon ECR 地址。即 URL 去掉 https
  • 配置登录信息。Token 参考上面手工获取到的结果

具体配置案例,参考下面的 nginx.conf 文件:

server {
    listen 80;
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size
    client_max_body_size 0;
    chunked_transfer_encoding on;
    location / {
    proxy_pass                          "https://111111111111.dkr.ecr.region-code-1.amazonaws.com";
    proxy_set_header  Host              "111111111111.dkr.ecr.region-code-1.amazonaws.com";
    proxy_set_header  Authorization     "Basic TOKEN_HERE";       
    proxy_set_header  X-Real-IP         $remote_addr; 
    proxy_set_header  X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header  X-Forwarded-Proto "https";
    proxy_read_timeout                  900;
    }
}

配置好后即可通过该 Nginx 实例,通过自定义域名访问 Amazon ECR 服务。不过,该方案中 Token 有效期默认 12 小时。我们还需要定期刷新 token 并更新 nginx 配置。可以通过 crontab 定时任务允许下面命令片段:

# 获取新 token
TOKEN=aws ecr get-authorization-token | jq -r ".authorizationData[].authorizationToken" 

# 更新 nginx 配置文件
sed -e "s/Basic.*/Basic $TOKEN\"\;/" /path/to/nginx.conf

# nginx 更新配置
sudo nginx -s reload

做完以上工作,一个基础的 Amazon ECR Nginx 反向代理部署完毕。

容器化反向代理方案

手工配置相对来说比较复杂,容易出错,后期维护也麻烦。幸运的是,已经有不少开源方案帮助我们实现了容器化“一键安装”。下面我们就介绍相对使用广泛的一个开源方案 aws-ecr-http-proxy 以及对应的 helm 安装 charts,简单的部署过程如下:

# 下载 helm values 文件
wget https://github.com/evryfs/helm-charts/blob/master/charts/ecr-proxy/values.yaml

# 编辑 values.yaml 配置文件,主要是 `env` 部分环境变量设置,关键点:
#   UPSTREAM:  ECR 的  https 地址
#   AWS_REGION:  region代码
#   RESOLVER:  DNS 解析地址,根据实际情况设置,例如 Amazon Web Services VPC DNS, 或者统一的域名服务器 "8.8.8.8"
#   PORT: nginx监听端口。 一般 “80”
#
# services.type 可以设置为 LoadBalancer,自动生成并配置相应ELB
#
# 如果需要启用TLS,还需要配置相应的 kubernetes config map, 用于存储证书文件
vim values.yaml
	
# 安装ECR proxy反向代理
helm repo add evryfs-oss https://evryfs.github.io/helm-charts/
helm install -f values.yaml evryfs-oss/ecr-proxy --name ecr-proxy --namespace ecr-proxy
	
# 获取对应的服务地址
kubectl get services -A | awk '/ecr-proxy/ {print $5}'

values.yaml 修改样例:

# -- Kubernetes service
service:
  #  type: ClusterIP
  type: LoadBalancer
  port: 80
	
# -- ingress configuration
ingress:
  enabled: false
  annotations: {}
    # kubernetes.io/ingress.class: nginx
    # kubernetes.io/tls-acme: "true"
  hosts:
    - host: registry.mycompany.com
    paths: []
  tls: []
  #  - secretName: chart-example-tls
  #    hosts:
  #      - chart-example.local
env:
  - name: UPSTREAM
  value: 111111111111.dkr.ecr.region-code-1.amazonaws.com
  - name: AWS_REGION
  value: region-code-1
  - name: RESOLVER
  value: "8.8.8.8"
  - name: PORT
  value: "80"

如果我们需要 HTTPS/TLS,可以参考官方文档自行配置。安装配置完成后,命令将输出对应 services 的服务地址 URL。我们即可以通过该地址访问 Amazon ECR。

统一域名配置

当我们在所有 Region 都部署完毕 Amazon ECR Nginx 反向代理后, 我们可以获取到 Nginx 的访问地址。然后,我们就可以通过 Amazon Route 53 CNAME 记录来配置统一域名,通过 geolocation(按地理位置)/ latency(按时延) 解析策略实现就近访问。具体配置细节可以参考下图。也可以参考 Amazon Route 53 地理位置记录的特定值按需求自行设置。

测试验证

做完上述部署后,我们可以通过以下命令进行简单的功能验证 Amazon ECR 登录、拉取镜像功能。

# 登录验证
aws ecr get-login-password | sudo docker login -u AWS --password-stdin registry.mycompany.com

# 下载镜像
sudo docker pull registry.mycompany.com/myimage:tag

注意,反向代理方案不支持 push 镜像。原因是 push 时 image 所打的 tag 带的是自定义域名,而非 Amazon ECR 原生域名。所以,我们的 CI/CD 侧,在 push 镜像时,仍然需要使用 Amazon ECR 原有域名。

总结

借由 Amazon ECR 跨区域同步、Nginx 反向代理、Amazon Route 53 Geolocation解析,该方案解决了在多区域场景,快速、简单、统一的容器仓库访问需求,加速客户 Devops 全球化落地,让 Devops 飞起来。

参考文档

1.  Amazon ECR 官方文档

2. Amazon Route 53 官方文档

3. Helm 使用帮助文档

4. Amazon ECR HTTP proxy 开源项目

本篇作者

刘科

西云数据解决方案架构师,15+ 年 IT 研发、管理经验,曾就职于知名通信设备厂商、头部互联网企业,有丰富、广泛的系统架构与研发管理经验。擅长帮助企业打造完善研发 Devops 体系,构建网络与安全体系,帮助企业业务产品项目落地。

许庭新

解决方案架构师,10+ 年产品研发和解决方案咨询经验,在电商,互联网金融,智能汽车领域有丰富的实战经验,擅长利用云计算、大数据,AI 等技术挖掘用户底层需求,实现精准运营。