亚马逊AWS官方博客

基于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容器技术,更加轻松地构建和管理您的应用程序,发挥更大的效益。

作者介绍

李磊,AWS解决方案架构师,负责基于AWS的云计算方案的架构设计,同时致力于AWS云服务在国内和全球的应用和推广。在大规模并发后台架构,电商系统,社交网络平台、互联网领域应用,DevOps以及Serverless无服务器架构等领域有着广泛的设计与实践经验。在加入AWS之前超过十年的开发和架构设计经验, 带领团队攻克各种技术挑战,总是希望站在技术的最前沿。