基于Amazon EC2 Container Service构建安全高可用的Docker私有库
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镜像的容器,它是官方提供的用于构建镜像仓库服务的镜像文件。
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://${SSLCertificateFileBucket}/${SSLCertificateFileName} /docker-registry/certs/domain.crt aws s3 cp s3://${SSLCertificateFileBucket}/${SSLKeyFileName} /docker-registry/certs/domain.key |
aws s3 cp s3://${SSLCertificateFileBucket}/${HtpasswdFileName} /docker-registry/auth/htpasswd
/opt/aws/bin/cfn-signal -e $? –stack ${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相关的主要模板代码如下:
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相关的主要模板代码如下:
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证书
yourcertificate.crt intermediate-certificates.pem > yourcertificate.crt |
Docker官方建议私有库数据传输基于TLS,这样您还需要为您的域名申请CA认证,得到TLS证书和秘钥文件。有以下两种方式:
向CA供应商购买。
利用Let’s Encrypt生成免费的证书,具体操作参考letsencrypt官网。
另外,如果您的证书供应商还提供给你intermedia证书,那么您需要将它与你的证书进行合并,运行如下命令可以进行合并
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
{
“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” |
}
} |
openssl x509 -inform DER -in Certificate.der -outform PEM -out Certificate.pem |
当Docker客户端向我们的ELB发送请求时,出于安全考虑,需要对传输加密,这时可以利用IAM Server Certificate,将我们的TLS证书、秘钥以及证书链文件上传,IAM会帮我们自动生成一个Server Certificate,再将它与ELB绑定即可。
可以使用AWS CLI按照以下命令上传:
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文件
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镜像。
docker pull ubuntu:16.04 |
然后tag这个镜像到我们自己的域名下
docker tag ubuntu:16.04 myregistrydomain.com/my-ubuntu |
登录我们的私有库
docker login myregistrydomain.com |
输入用户名,密码后,调用push命令将镜像上传
docker push myregistrydomain.com/my-ubuntu |
上传完后,可以调用pull命令拉取镜像
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容器技术,更加轻松地构建和管理您的应用程序,发挥更大的效益。