镜像仓库

[TOC]

### 基本原理
Docker Registry是开源的镜像请求的处理端，用于封装的镜像的存取。Docker客户端通过访问Registry推送（push）或拉取（pull）镜像。

#### HTTPS
Registry可提供HTTPS或HTTP服务，而Docker客户端默认通过HTTPS访问Registry，因此有以下几种方案：
1. Registry仅提供HTTP服务，客户端需在docker配置中添加`--insecure-registry`选项，即客户端无条件信任Registry，并需重启Docker。
2. Registry添加经过CA认证的SSL证书，需通过CA付费申请。客户端无需修改任何配置。
3. Registry添加自签名SSL证书，手动生成证书，客户端需在指定目录下存放证书。
4. 针对方案2及方案3，在Registry前端提供一个代理用以处理SSL，代理与Registry之间通过HTTP通信。

**注意**：
1. 目前采取的是方案4中对方案3的优化，即用户需下载并放置证书，服务端需搭配一套Nginx代理。
2. HTTPS在镜像传输的过程中对数据加密，是客户端（Docker Client）信任服务端（Registry）。
3. HTTPS与用户对Registry的访问鉴权没有关联。

#### 鉴权
Docker Registry的鉴权基于OAuth2.0协议，具体的过程如下：

<img width="326" alt="docker-registry_workflow" src="https://qiita-image-store.s3.amazonaws.com/0/194999/0fb3c735-472e-8e1d-62a9-038fb1900245.png">


1. Docker Client（Docker命令行或Pod拉取镜像时调用的Docker API）请求Registry，要求访问指定的镜像资源。
2. Registry返回401错误，并在响应头部Www-Authenticate指示鉴权方式，包括鉴权服务器的地址、鉴权服务名、访问的资源范围等。
3. Docker Client带着鉴权信息（用户名密码等），根据指示请求鉴权服务器。
4. 如果鉴权通过，鉴权服务器返回token给Docker Client。
5. Docker Client带着token进行与第一步类似的请求。
6. Registry解析token后，对指定的资源请求予以放行。

**注意**：
1. 过程中Registry与鉴权服务器不直接交互，他们共享一套密钥证书对。
2. 鉴权服务器使用该密钥证书对将镜像资源请求的具体内容加密成token，Registry利用同样的密钥证书对将其解密。

#### 通知
Docker Registry的通知机制允许Registry在其镜像资源被请求完成后（拉取、推送、列表等），对指定的地址发送HTTP请求来记录该次操作，因此我们可以利用这个功能，以收到通知的时机为准，将推送到Registry的镜像记入DB，或对镜像进行跨区域同步。对每个镜像来说，每个layer的推送均会发送通知，因此通知的量会较大，通知接收端需要对其进行过滤。只有最后推送完成时通知的target字段才不为空，从而可以根据此来过滤通知消息。

#### 存储
Registry可以对接多种存储，如内存、硬盘（本地盘、NFS盘或NAS盘等）、对象存储（Swift、S3等）等，目前大部分采用的是NAS盘。
##### OBS
之前内部云尝试过对接OBS对象存储，希望将镜像数据的同步交由存储端完成，遇到了如下问题：
1. 底层基于ceph搭建。
2. ceph的gateway提供API访问，包括完整的Swift接口（已顺利对接，但由于OBS团队没有对Swift接口进行产品化封装，因此该方案被否决）及部分完整的S3接口。
3. ceph的gateway提供的S3接口与标准的S3接口相比有明显缺失（如缺少Registry接到推送镜像时会调用S3的批量删除blob的接口），因此OBS团队在gateway上层又配置了一层代理，专门用于处理缺失的接口（如批量删除blob），但仍对接失败，后未查明原因。

### 公有云内部镜像库
1. 用于云平台公有云内部使用，无鉴权，暂无通知，计划要有跨区域同步功能（需启用通知功能）。
2. 每个地域（EC/SC/HK）一套Registry，同一个地域内不同可用区共用同一套（ECA/ECB）。
3. 每套Registry的存储对接一块NAS盘，两个实例高可用。

#### 架构
![](/uploads/201808/attach_154a6166030d0a33.png)

#### 配置
启动脚本：
```
docker run -d -p 80:5000 \
           --restart=always --name registry \
           -v /docker/regsitry:/var/lib/registry \
           -v /root/registry/config:/etc/registry \
           registry:2.5.0 \
           serve "/etc/registry/config.yml"
```

config.yml：
```
version: 0.1
log:
  level: debug
  formatter: json
  fields:
    service: registry
storage:
    cache:
        layerinfo: inmemory
        blobdescriptor: inmemory
    filesystem:
        rootdirectory: /var/lib/registry
    maintenance:
        uploadpurging:
            enabled: false
    delete:
        enabled: true
http:
    addr: :5000
    secret: placeholder
    # must be https + domain name to avoid http patch 401 error
    host: https://hub.cloud.pub
    debug:
      addr: :5001

notifications:
  endpoints:
      - name: notify
        disabled: false
        # endpoint url must be internal ip or vip
        # zone VIP
        # 实验性地开启通知功能，通知发送到Docker Server
        url: http://100.68.3.22:8080/PUB/notify
        timeout: 300s
        threshold: 5
        backoff: 30s
```

### 公有云外部镜像库
1. 用于云平台公有云的客户及客户的k8s集群使用，有鉴权，有通知，计划要有跨区域同步功能。
2. 每个地域（EC/SC/HK）一套Registry，同一个地域内不同可用区共用同一套（ECA/ECB）。
3. 所有可用区的Registry共用同一套位于ECA云管区的镜像鉴权服务。
4. 每套Registry的存储对接一块NAS盘，两个实例高可用。

#### 架构
![](/uploads/201808/attach_154a689d3719a0ae.png)

#### 配置
启动脚本：
```
docker run -d -p 80:5000 \
           --restart=always --name registry \
           -v /docker/regsitry:/var/lib/registry \
           -v /root/registry/config:/etc/registry \
           registry:2.6.2 \
           serve "/etc/registry/config.yml"
```

config.yml：
```
version: 0.1
log:
  level: debug
  fields:
    service: registry
storage:
    cache:
        layerinfo: inmemory
        blobdescriptor: inmemory
    filesystem:
        rootdirectory: /var/lib/registry
    maintenance:
        uploadpurging:
            enabled: false
    delete:
        enabled: true
http:
    addr: :5000
    secret: placeholder
    # must be https + domain name to avoid http patch 401 error
    host: https://hub.cloud.papub
    debug:
        addr: :5001

auth:
    token:
      issuer: caas-issuer
      # realm must be domain name
      realm: http://auth.cloud.papub:8080/ECA/token
      rootcertbundle: /etc/registry/root.crt
      service: caas-registry

notifications:
  endpoints:
      - name: notify
        disabled: false
        # endpoint url must be internal ip or vip
        # zone VIP
        url: http://100.68.3.15:8080/ECA/notify
        timeout: 300s
        threshold: 5
        backoff: 30s
```

#### 互联网访问
在公有云外部Registry的实例宿主机上，5000端口也起了Registry实例，但配置略微不同，通过与枢纽代理配合提供对互联网的镜像服务。互联网用户访问的镜像数据及鉴权服务的最终提供方是相同的，但两者的访问链路不同。
##### 架构
![](/uploads/201808/attach_154ab2ae999bbabe.png)


##### 配置
启动脚本：
```
docker run -d -p 5000:5000 \
           --restart=always --name registry_external \
           -v /docker/regsitry:/var/lib/registry \
           -v /root/registry_external/config:/etc/registry \
           registry:2.6.2 \
           serve "/etc/registry/config.yml"
```

config.yml：
```
version: 0.1
log:
  level: debug
  formatter: json
  fields:
    service: registry
storage:
    cache:
        layerinfo: inmemory
        blobdescriptor: inmemory
    filesystem:
        rootdirectory: /var/lib/registry
    maintenance:
        uploadpurging:
            enabled: false
    delete:
        enabled: true
http:
    addr: :5000
    secret: placeholder
    # must be https + domain name to avoid http patch 401 error
    host: https://eca-hub.yun.pingan.com

auth:
    token:
      issuer: caas-issuer
      # realm must be domain name
      realm: http://hub-auth.yun.pingan.com/ECA/token
      rootcertbundle: /etc/registry/root.crt
      service: caas-registry

notifications:
  endpoints:
      - name: notify
        disabled: false
        # endpoint url must be internal ip or vip
        # zone VIP
        url: http://100.68.3.15:8080/ECA/notify
        timeout: 300s
        threshold: 5
        backoff: 30s
```


### 内部云镜像库
1. 用于云平台内部云的内部、客户及客户的k8s集群使用，有鉴权、无通知、无需跨区域同步功能。
2. 每个可用区一套Registry Proxy，请求最终由位于SZC云管区的Registry处理。
3. Registry Proxy同时作为鉴权Proxy，将鉴权的请求转发到SZC云管区的Kube Manager（合并原Docker Server）。
4. Registry的存储对接一块NAS盘，两个实例高可用。

#### 架构
![](/uploads/201808/attach_154a60e2b5a54197.png)

#### 配置
Registry docker-compose.yml：
```
version: '2'
services:
  registry:
    ports:
      - 80:80
    image: docker.io/registry:2.6.2
    restart: always
    volumes:
      - /Docker/registry:/var/lib/registry
      - ./config/:/etc/registry/
    environment:
      - GODEBUG=netdns=cgo
    command:
      ["serve", "/etc/registry/config.yml"]
```

Registry config.yml：
```
version: 0.1
log:
  level: debug
  formatter: json
  fields:
    service: registry
  accesslog:
    disabled: true
storage:
    cache:
        layerinfo: inmemory
    filesystem:
        rootdirectory: /var/lib/registry
    maintenance:
        uploadpurging:
            enabled: false
    delete:
        enabled: true
http:
    addr: :80
    host: https://hub.yun.paic.com.cn
    secret: placeholder

auth:
  token:
    issuer: caas-issuer
#    realm: http://ds.yun.paic.com.cn:8080/api/token
    realm: http://ds.yun.paic.com.cn:9090/api/token
    rootcertbundle: /etc/registry/root.crt
    service: caas-registry
```

Proxy docker-compose.yml：
```
nginx:
  log_driver: json-file
  image: "library/nginx:1.10-auth-request"
  ports:
    - 443:443
#    - 8080:8080
    - 9090:9090
    - 80:80
  volumes:
    - ./nginx.conf:/etc/nginx/nginx.conf
    - ./config:/etc/nginx/conf.d
    - ./log:/var/log/nginx
```

Proxy reg_443.conf：
```
  upstream docker_registry {
    server 30.16.232.63:80;
  }
  server {
    access_log /var/log/nginx/reg_443_access.log;
    listen 443 ssl;
    server_name hub.yun.paic.com.cn;
    ssl_certificate /etc/nginx/conf.d/domain.crt;
    ssl_certificate_key /etc/nginx/conf.d/domain.key;
    ssl on;
    ssl_protocols TLSv1.1 TLSv1.2;
    ssl_session_cache shared:SSL:1m;
    ssl_session_timeout 5m;
    ssl_ciphers  HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers   on;

    client_max_body_size 0;
    chunked_transfer_encoding on;
    location /v2/ {
      proxy_pass         http://docker_registry;
      proxy_redirect     off;
      proxy_set_header   Host             $host;
      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  $scheme;
      proxy_read_timeout 900;
    }
  }
```

Proxy reg_80.conf：
```
  upstream docker_registry_80 {
    server 30.16.232.63:80;
  }
  server {
    access_log /var/log/nginx/reg_80_access.log;
    listen 80;
    server_name hub.yun.paic.com.cn;
    client_max_body_size 0;
    chunked_transfer_encoding on;
    location / {
      proxy_pass 	 http://docker_registry_80;
      proxy_redirect     off;
      proxy_set_header   Host             $host;
      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  $scheme;
      proxy_read_timeout 900;
    }
  }
```

Proxy ds_9090.conf：
```
  upstream docker_server {
    server 30.16.232.67:8080;
  }
  server {
    access_log /var/log/nginx/ds_access.log;
    listen 9090;
    server_name ds.yun.paic.com.cn:9090;
    client_max_body_size 0;
    chunked_transfer_encoding on;
    location / {
      proxy_pass http://docker_server;
      proxy_redirect     off;
      proxy_set_header   Host             $host;
      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  http;
    }
  }
```

### 同步设计
Docker Server早期设计的镜像同步功能比较简单，主要利用Registry的通知功能针对内部云的跨区域镜像同步，后来随着内部云镜像库的架构优化，镜像同步功能在内部云已经暂时失去了意义。
#### 过程
1. 本区域Registry通知Docker Server，Docker Server解析通知，action为push，且target不为空，说明本区域Registry刚刚接收了一次镜像push。
2. Docker Server检查通知内容中的推送者IP，如果该IP属于待分发列表中的某个远端目标Registry，则不进行分发，防止分发回环。
3. Docker Server比对通知内容中的镜像名与配置的镜像分发白名单，如果镜像名符合白名单规则则继续处理。
4. Docker Server调用分发服务器上的Docker 客户端的Socket接口：
  1. 从本区域Registry拉取刚刚推送的镜像，如local/tomcat:8。
  2. 将该镜像tag成远端目标Registry的镜像名，如remote/tomcat:8。
  3. 将tag后的镜像推送到远端目标Registry。

#### 问题
目前在上述过程的第4步调用Docker客户端的接口有问题，预计是由于使用的Docker Client API的版本导致的问题。

### 思考
目前内部云通过Registry Proxy的方式将镜像汇总到同一个Registry，随着用户使用量的增大，Registry Proxy到Registry之间的链路压力会变得非常大。 因此针对内部云，后期可以考虑以下几个方向的探索：
1. 采用Registry提供的Reigstry mirror/pull cache等配置，在各个区域搭建mirror/cache，mirror/cache按需与汇总区的Registry进行交互，减小目前总Registry的压力。
2. 类似于迅雷的p2p方式，在同区域内点对点传输，但目前以云平台内部的网络架构来看不太可能。

而公有云方面，由于各区域有单独的Registry，因此网络压力不会太大，最大的问题是镜像跨区域同步的问题，根据需求来看，后期可以考虑：
1. 利用Docker Server早期设计的简单同步功能，配置一些同步规则，进行按需同步。
2. 搭建harbor进行同步。

### 上新区指引
#### 公有云内部镜像库
1. 在新区的云管区SF搭建Registry：
	1. 映射主机80端口。
	2. 多个实例使用同一块NAS盘。
2. 根据用户所在区域（公共服务区或云管区SF）给Registry配置LVX：
	1. 需配置LVS VIP并由Nginx监听。
	2. Nginx需配置：监听443及80端口（443端口配置SSL证书秘钥）、转发到Registry实例所在主机的80端口、修改请求体上限、添加Upstream的ip_hash。
3. 根据内部用户所在区域（公共服务区或云管区SF），配置相应区域的DNS，将hub.cloud.pub域名解析到VIP。
4. 开通防火墙策略：
	1. 用户VPC网段 -> VIP 80/443端口。
	2. Nginx集群 -> Registry实例所在主机 80端口。
5. 用户的机器需在`/etc/docker/hub.cloud.pub/ca.crt`放置SSL证书。

#### 公有云外部镜像库
1. 在新区的云管区SF搭建Registry：
	1. 映射主机80端口。
	2. 多个实例使用同一块NAS盘。
	3. notification的endpoint：`<Docker Server的VIP>:<端口>/<新区code>/notify`。
	4. auth的realm：`http://auth.cloud.papub/<新区code>/token`。
2. 根据用户所在区域（通常为公共服务区）给Registry配置LVX：
	1. 需配置LVS VIP并由Nginx监听。
	2. Nginx需配置：监听443及80端口（443端口配置SSL证书秘钥）、转发到Registry实例所在主机的80端口、修改请求体上限、添加Upstream的ip_hash。
	3. Nginx需配置：监听8080端口、转发到Docker Server实例所在主机8080端口。
3. 根据外部用户所在区域（通常是公共服务区），将hub.cloud.papub及auth.cloud.papub域名解析到VIP。
4. 开通防火墙策略：
	1. 用户VPC网段 -> VIP 80/443端口。
	2. Nginx集群 -> Registry实例所在主机 80端口。
	3. Nginx集群 -> Docker Server实例所在主机 8080端口。
5. 用户的机器需在`/etc/docker/hub.cloud.papub/ca.crt`放置SSL证书。

#### 公有云互联网镜像库
1. 在外部镜像库所在主机上启动新的Registry：
	1. 映射主机5000端口。
	2. 与外部镜像库共用NAS盘。
	3. notification的endpoint：`<Docker Server的VIP>:<端口>/<新区code>/notify`。
	4. auth的realm：`http://hub-auth.yun.pingan.com/<新区code>/token`。
1. 申请新区的公网IP。
2. 申请公网域名xxx-hub.yun.pingan.com，解析到新申请的公网IP。
3. 在新区云管区DMZ区域内搭建枢纽代理Nginx：
	1. 监听某两个端口（如9443与9080），转发到Registry实例所在主机的5000端口、修改请求体上限、添加Upstream的ip_hash。
4. 在新区F5上配置：
	1. caas镜像库资源池，指定IP为枢纽代理Nginx。
	2. 公网IP的443端口配置`*.yun.pingan.com`的SSL证书，可从其他区域复制。
	3. 公网IP的443及80端口转发到枢纽代理监听的端口（如80->9080, 443->9443）。
5. 开通防火墙策略：
	1. 枢纽代理 -> Registry实例所在主机 5000端口。
6. 用户的机器不需要在`/etc/docker/`放置任何SSL证书，因为证书与公有云的公网域名的证书有效性一致。

#### 内部云镜像库
1. 在新区的公共服务区搭建Registry Proxy，建议以后的用LVX替代：
	1. 配置LVS VIP并由Nginx监听。
	2. Nginx需配置：监听443及80端口（443端口配置SSL证书秘钥）、转发到Registry实例所在主机的80端口、修改请求体上限、添加Upstream的ip_hash。
	3. Nginx需配置：监听9090端口，转发到Manager实例所在主机的8080端口。
2. 将整个新区的hub.yun.paic.com.cn及ds.yun.paic.com.cn域名解析到VIP。
3. 开通防火墙策略：
	1. 用户VPC -> Proxy 80/443/9090端口。
	2. Proxy或Nginx集群 -> SZC云管区Registry实例所在主机 80端口。
	3. Proxy或Nginx集群 -> SZC云管区Manager实例所在主机 8080端口。
4. 用户的机器需在`/etc/docker/hub.yun.paic.com.cn/ca.crt`放置SSL证书。