1. 背景
Docker hub 作为 docker 官方镜像仓库,提供了大量 Docker 镜像的托管服务。但使用它来托管企业私有 Docker 镜像存在一些问题,比如:
(1)Docker hub 托管私有镜像的服务目前只面对收费账户;
(2)使用 Docker hub 托管私有镜像并不适合一些特殊场景,比如工作环境网络是内网,不允许访问外网,那就更不可能到 Docker hub 去下载镜像。
在这种情况下,如果能构建一个安全可靠的 Docker 私有库,将会是一个更好的选择。本文将介绍在 Amazon EC2 Container Service 基础上结合 AWS Elastic LoadBalancer、AWS Autoscaling Group、AWS S3 及 Docker 官方提供的 registry 镜像构建安全、高可用的 Docker 私有库的方案,帮助您轻构实现这一需求。
2. 方案详解
我们会使用 AWS CloudFormation 服务,使用自定义的模板脚本声明所需的资源,实现自动化构建。接下来结合我们的模板脚本对本方案进行详细介绍。
注意:以下内容与代码相关部分只贴出主要代码,部分代码用…表示省略;红字部分请替换成您自己账号相关的信息。
完整模板代码地址:https://s3-us-west-2.amazonaws.com/blog.leonli.org/registry.yml
或者点击按钮直接在控制台中运行:
2.1 架构图
根据以上架构图,基本数据传输过程为:
(1)Docker 客户端向镜像仓库发送的 pull/push 等命令事实上都是通过 docker daemon 转换成 restful 请求的形式再发送给镜像仓库的。在本架构中,我们利用 AWS Elastic LoadBalancer(简称 ELB)接收客户端发来的请求,作为整个架构的接入层。由于我们要求数据是通过 TLS 加密传输的,所以我们需要使用 AWS IAM 中的 server certificate(由 AWS IAM 账户上传的 TLS 证书)与 ELB 关联,实现对客户端发来的请求进行加密。
(2)ELB 会将请求反向代理给后端分布在不同可用区的两台 Container Instance(安装了 Docker 运行环境的 EC2 实例),Container Instance 中运行了 Docker registry 服务。当请求到达 registry 时,我们需要首先使用内置在 registry 中的用户认证文件(比如本架构中使用 apache htpasswd 创建的基本用户名密码保护文件),进行用户认证,认证不通过,则驳回请求,认证通过,才可以读写数据。
(3)我们将数据统一存储在一个只供创建者使用的 S3 Bucket 中。
2.2 基于 AWS ECS 运行 Docker Registry 服务
Amazon EC2 Container Service (ECS) 是一项高度可扩展的高性能容器管理服务,它让您能够在托管的 Amazon EC2 实例群集上轻松运行 Docker 应用程序。 Amazon ECS 主要有以下几个组件:ECS Cluster、 Container Instance、Task , ECS Service。这里我们基于 ECS 运行了 Docker registry 服务,架构如下:
(1)首先我们在模板中定义了一个 ECS Cluster,用来管理相关的 Container Instance。ECS 提供了 ECS-Optimize AMI 来创建 EC2 实例作为 Container Instance,ECS-Optimize AMI 已经内置 Docker 运行环境和 Container Agent 代理,可以为我们节省安装这些环境所需的时间。Container Instance 在启动时可以由 Container Agent 根据配置文件/etc/ecs/ecs.config 中的 ClusterName 属性的值知道需要将实例注册到哪个 ECS Cluster 上。
因为我们要使用 Auto Scaling 服务实现对 EC2 实例的伸缩控制。所以我们使用 Auto Scaling 的 Launch Config 组件声明我们的 Container Instance。并通过 UserData 传入 Shell 脚本,此脚本主要完成以下三件事:
– 调用 echo ECS_CLUSTER=${ECSCluster} >> /etc/ecs/ecs.config ,将 Container Instance 注册到我们的 ECS Cluster 中。
– 创建/docker-registry/certs 目录和/docker-registry/auth 目录,并调用 aws s3 copy 命令从指定的 S3 Bucket 中复制 TLS 证书文件和 htpasswd 用户认证文件。这些文件将在运行 Docker Registry 时使用到。
– 调用 cfn-signal 命令,通知 AutoScaling Group 资源 EC2 实例已经启动完毕。
Container Instance 相关的主要模板代码如下:
(2)然后我们定义了一个 TaskDefinition 来声明我们要运行的容器及其相关配置。这里我们运行的是 Docker registry 镜像的容器,它是官方提供的用于构建镜像仓库服务的镜像文件。
col 1
ContainerInstances:
Type: AWS::AutoScaling::LaunchConfiguration
Properties:
…
UserData:
Fn::Base64: !Sub |
#!/bin/bash -xe
echo ECS_CLUSTER=${ECSCluster} >> /etc/ecs/ecs.config
yum install -y aws-cfn-bootstrap
yum install -y aws-cli
sudo rm -rf /docker-registry
sudo mkdir -p /docker-registry/certs
sudo chmod 777 /docker-registry/certs
sudo mkdir -p /docker-registry/auth
sudo chmod 777 /docker-registry/auth
aws s3 cp s3://{SSLCertificateFileName} /docker-registry/certs/domain.crt
aws s3 cp s3://{SSLKeyFileName} /docker-registry/certs/domain.key
aws s3 cp s3://{HtpasswdFileName} /docker-registry/auth/htpasswd
/opt/aws/bin/cfn-signal -e {AWS::StackId} –resource ECSAutoScalingGroup –region ${AWS::Region}
– 配置环境变量 REGISTRY_AUTH、REGISTRY_AUTH_HTPASSWD_REALM、REGISTRY_AUTH_HTPASSWD_PATH 指定宿主机目录/auth/htpasswd 文件作为 Basic Authentication 基础用户认证文件,从而实现用户授权认证。
– 配置环境变量 REGISTRY_HTTP_TLS_CERTIFICATE、REGISTRY_HTTP_TLS_KEY 指定宿主机目录/certs/中存放的 domain.crt 作为 TLS 证书文件,domain.key 作为 TLS 秘钥,从而实现 TLS 传输加密。
– 通过配置环境变量 REGISTRY_STORAGE 指定 registry 的存储驱动为 AWS S3,配置 REGISTRY_STORAGE_S3_REGION 为存储的 S3 所在 Region,配置 REGISTRY_STORAGE_S3_BUCKET 为存储的 Bucket。从而实现将镜像文件存储到 AWS S3 指定的 Bucket 中。
关于构建私有库的更多细节和更多配置可以通过 Docker 官方文档进行了解:Deploy a registry server。
TaskDefinition 相关的主要模板代码如下:
col 1
RegistryTaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
NetworkMode: bridge
ContainerDefinitions:
– Name: RegistryContainer
Image: registry:2
…
PortMappings:
– ContainerPort: 443
HostPort: 443
…
Environment:
– Name: REGISTRY_AUTH
Value: htpasswd
– Name: REGISTRY_AUTH_HTPASSWD_REALM
Value: “Registry Realm”
– Name: REGISTRY_AUTH_HTPASSWD_PATH
Value: /auth/htpasswd
– Name: REGISTRY_HTTP_TLS_CERTIFICATE
Value: /certs/domain.crt
– Name: REGISTRY_HTTP_TLS_KEY
Value: /certs/domain.key
– Name: REGISTRY_HTTP_ADDR
Value: 0.0.0.0:443
– Name: REGISTRY_STORAGE
Value: s3
– Name: REGISTRY_STORAGE_S3_REGION
Value: !Ref AWS::Region
– Name: REGISTRY_STORAGE_S3_BUCKET
Value: !GetAtt StorageBucket.DomainName
…
(3)最后我们定义了一个 ECS Service,指定 TaskDefinition、Cluster 和所需运行的任务个数 DesiredCount。这样,我们就构建了一个运行着 docker registry 镜像的 ECS 服务了。
2.2 如何实现高可用性
(1) 跨可用区部署服务
– 首先,我们在模板中声明了一个 VPC 和两个私有子网 PrivateSubnet1 和 PrivateSubnet2,这两个子网是分别部署在不同可用区的。
– 其次,我们的 ECS 服务是通过 Elastic Load Balancing(ELB)来平衡多个容器的负载,ELB 是高可用且自动伸缩的。我们在模板中定义了一个命名为 RegistryELB 的 ELB 组件,指定它是 internet-facing 模式可供外网访问、并且是跨可用区的。ELB 接收外网的请求,并且将请求代理给 Container Instance 中的容器。
– 最后,我们在模板中声明了一个 Auto Scaling Group,指定 VPCZoneIdentifier 为跨可用区的两个子网 PublicSubnet1 和 PublicSubnet2,由 RegistryELB 代理请求,从而实现跨可用区部署服务。
(2) 利用 Auto Scaling 保障可用实例数量
当服务遇到一些突发或者预期的高流量时,或者您的服务出现某些异常时,可以利用 Auto Scaling 服务保障可用实例数量。比如某台 Container Instance 宕机了,那么可以利用 Auto Scaling 自动启动相同配置的另一台 Container Instance 代理宕机的实例继续提供服务。
大部分情况下,企业 Docker 私有库承受的流量负载不会太大,所以本方案不介绍 Auto Scaling 的扩展策略,当然您也可以根据自己的业务需要修改模板代码,实现此功能。本方案使用 Auto Scaling 主要是为了保障可用实例数量一直维持在 DesiredCapacity,这个参数是我们通过模板的参数传入的。
Auto Scaling Group 相关的主要模板代码如下:
col 1
ECSAutoScalingGroup:
Type: AWS::AutoScaling::AutoScalingGroup
…
Properties:
…
LaunchConfigurationName: !Ref ‘ContainerInstances’
MinSize: !Ref MinSize
MaxSize: !Ref MaxSize
DesiredCapacity: !Ref DesiredCapacity
…
(3) 利用 s3 确保数据完整性
Amazon Simple Storage Service (Amazon S3) 是 AWS 提供的对象存储服务,它可用于在 Web 上的任何位置存储和检索任意数量的数据,能够提供 99.999999999% 的持久性,使用 S3 来存储 Docker 私有库的镜像文件,可以确保数据完整。Docker registry 镜像支持使用 S3 作为存储驱动,只需传入存储所在 Bucket 和所在 Region 即可。我们在模板中声明了一个 Bucket 用来存储镜像数据。并且这个 Bucket 只能由创建者进行控制。
2.3 如何实现安全性
(1) 利用 TLS 加密传输
Docker 官方建议与私有库通信的所有数据传输皆使用 TLS 加密,我们通过上传 IAM server certificate 证书并将其与 ELB 关联,ELB 的 Listener 使用 SSL 安全监听 443 端口,并将请求代理给实例的 443 端口,实例上也需要安装 TLS 证书。从而实现全链路的安全传输。
(2)利用 apache htpasswd 实现基本用户认证
如果我们创建的私有库没有用户认证机制,那么无论是谁只要知道私有库链接,就可以肆无忌惮地访问和操作我们托管在私有库的文件,这显然不是我们想要看到的。所以需要实现用户认证,只有通过认证的用户,才有权进行相关操作。
这里,我们使用 apache htpasswd 实现基本用户认证。这也是 Docker registry 镜像默认支持的认证方式。
3. 构建过程
3.1 准备工作
(1) 域名
您必须拥有一个用来指向 registry 的域名,这样您才可以通过域名访问您的私有库。
(2)TLS 证书
col 1
yourcertificate.crt intermediate-certificates.pem > yourcertificate.crt
Docker 官方建议私有库数据传输基于 TLS,这样您还需要为您的域名申请 CA 认证,得到 TLS 证书和秘钥文件。有以下两种方式:
向 CA 供应商购买。
利用 Let’s Encrypt 生成免费的证书,具体操作参考letsencrypt官网。
另外,如果您的证书供应商还提供给你 intermedia 证书,那么您需要将它与你的证书进行合并,运行如下命令可以进行合并
col 1
aws iam upload-server-certificate —server-certificate-name YourCertName —certificate-body file:///path/to/certificate.pem —certificate-chain file:///path/to/chained.pem —private-key file:///path/to/private_key.pem
(3)上传 IAM Server Certificate
col 1
{
“ServerCertificateMetadata”: {
“Path”: “/”,
“ServerCertificateName”: “MyRegistryCert”,
“ServerCertificateId”: “ASCAJHAWE3QRHEHH3L6KM”,
“Arn”: “arn:aws:iam::xxxxxxxxx:server-certificate/YourCertName”,
“UploadDate”: “2017-07-01T16:16:45.125Z”,
“Expiration”: “2017-09-26T12:15:00Z”
}
}
col 1
openssl x509 -inform DER -in Certificate.der -outform PEM -out Certificate.pem
当 Docker 客户端向我们的 ELB 发送请求时,出于安全考虑,需要对传输加密,这时可以利用 IAM Server Certificate,将我们的 TLS 证书、秘钥以及证书链文件上传,IAM 会帮我们自动生成一个 Server Certificate,再将它与 ELB 绑定即可。
可以使用 AWS CLI 按照以下命令上传:
col 1
openssl rsa -in PrivateKey.pem -out PrivateKey.pem
上传完成后,您会得到类似以下响应信息,将红字部分 ARN 记录下来,后面构建时需要它作为参数传入模板中。
注意:
– TLS 证书、秘钥及证书链必须都是 PEM 格式,如果不是,可以使用以下命令进行转换:
– AWS IAM 要求证书秘钥是不加密的,所以当您的秘钥是加密时,需要使用以下命令转换
– 如果您在上传 Server Certificate 过程中遇到问题,可参考 AWS 官网指南:
Working with Server Certificate
(4)htpasswd 用户认证文件
使用 apache htpasswd 来做基本认证,首先需要安装 httpd,安装完后运行以下命令可以生成一个包含 一条用户名和密码记录的 htpasswd 文件
col 1
htpasswd -c username password > htpasswdfile
后面如果需要加入更多条记录,只需将 -c 参数去除,运行上面相同的命令即可。
(5) 将证书、秘钥和认证文件上传到 S3
我们需要将前面几步生成好的 TLS 证书、秘钥、htpasswd 文件上传到 S3 中,Container Instance 启动后会从 S3 中将这些文件拷贝下来,通过映射到容器中供 registry 容器使用。
创建一个 S3 Bucket, 将这几个相关文件上传。
3.2 自动化构建
接下来,我们在 AWS Management Console 中使用 CloudFormation 指定我们的模板文件创建一个 Stack。如何使用 CloudFormation 创建 Stack,请参考 AWS CloudFormation 用户指南:CloudFormation入门
Stack 构建成功后,在输出栏会有输出值 RegistryELBDns,它是我们创建的 ELB 的 DNS 域名。我们需要将我们之前 TLS 证书签发的域名 DNS 解析(CName 解析)转发到这个 ELB 的 DNS 域名。这样就可以使用我们自己的域名访问我们的私有库了。
3.3 开始使用
构建完 Docker 私有库后,现在可以开始使用了。
首先,在 Docker 客户端,我们可以从 Docker Hub 上拉取一个 ubuntu 镜像。
col 1
docker pull ubuntu:16.04
然后 tag 这个镜像到我们自己的域名下
col 1
docker tag ubuntu:16.04 myregistrydomain.com/my-ubuntu
登录我们的私有库
col 1
docker login myregistrydomain.com
输入用户名,密码后,调用 push 命令将镜像上传
col 1
docker push myregistrydomain.com/my-ubuntu
上传完后,可以调用 pull 命令拉取镜像
col 1
docker pull myregistrydomain.com/my-ubuntu
4. 总结
本文介绍了如何利用 AWS ECS 及其他 AWS 服务构建一个高可用、安全可靠的 docker 私有库,通过本方案的详细介绍和构建实践,相信您对于 docker registry 以及 AWS Elastic LoadBalancer、AWS Autoscaling Group、AWS S3 及 AWS CloudFormation 有了更进一步的认识。接下来,您可以利用 AWS ECS 容器管理服务及 Docker 容器技术,更加轻松地构建和管理您的应用程序,发挥更大的效益。
作者介绍
李磊,AWS 解决方案架构师,负责基于 AWS 的云计算方案的架构设计,同时致力于 AWS 云服务在国内和全球的应用和推广。在大规模并发后台架构,电商系统,社交网络平台、互联网领域应用,DevOps 以及 Serverless 无服务器架构等领域有着广泛的设计与实践经验。在加入 AWS 之前超过十年的开发和架构设计经验, 带领团队攻克各种技术挑战,总是希望站在技术的最前沿。
本文转载自 AWS 技术博客。
原文链接:
https://amazonaws-china.com/cn/blogs/china/using-amazon-ecs-to-build-private-docker-hub/
评论