Docker 映象加速教程

米開朗基楊發表於2021-02-26

原文連結:https://fuckcloudnative.io/posts/docker-registry-proxy/

在使用 Docker 和 Kubernetes 時,我們經常需要訪問 gcr.ioquay.io 映象倉庫,由於眾所周知的原因,這些映象倉庫在中國都無法訪問,唯一能訪問的是 Docker Hub,但速度也是奇慢無比。gcr.azk8s.cngcr.io 映象倉庫的代理站點,原來可以通過 gcr.azk8s.cn 訪問 gcr.io 倉庫裡的映象,但是目前 *.azk8s.cn 已經僅限於 Azure 中國的 IP 使用,不再對外提供服務了。國內其他的映象加速方案大多都是採用定時同步的方式來快取,這種方法是有一定延遲的,不能保證及時更新,ustc 和七牛雲等映象加速器我都試過了,非常不靠譜,很多映象都沒有。

為了能夠順利訪問 gcr.io 等映象倉庫,我們需要在牆外自己搭建一個類似於 gcr.azk8s.cn 的映象倉庫代理站點。利用 Docker 的開源專案 registry 就可以實現這個需求,registry 不僅可以作為本地私有映象倉庫,還可以作為上游映象倉庫的快取,也就是 pull through cache

先來感受下速度:

1. 前提條件


  • 一臺能夠施展魔法的伺服器(你懂得,可以直接訪問 gcr.io)
  • 一個域名和域名相關的 SSL 證書(docker pull 映象時需要驗證域名證書),一般用 Let's Encrypt 就夠了。

2. 核心思路


registry 可以通過設定引數 remoteurl 將其作為遠端倉庫的快取倉庫,這樣當你通過這個私有倉庫的地址拉取映象時,regiistry 會先將映象快取到本地儲存,然後再提供給拉取的客戶端(有可能這兩個步驟是同時的,我也不太清楚)。我們可以先部署一個私有 registry,然後將 remoteurl 設為需要加速的映象倉庫地址,基本上就可以了。

3. 定製 registry

為了能夠支援快取 docker.iogcr.iok8s.gcr.ioquay.ioghcr.io 等常見的公共映象倉庫,我們需要對 registry 的配置檔案進行定製,Dockerfile 如下:

FROM registry:2.6
LABEL maintainer="registry-proxy Docker Maintainers https://fuckcloudnative.io"
ENV PROXY_REMOTE_URL="" \
    DELETE_ENABLED=""
COPY entrypoint.sh /entrypoint.sh

其中 entrypoint.sh 用來將環境變數傳入配置檔案:

{{< expand "entrypoint.sh" >}}

#!/bin/sh

set -e

CONFIG_YML=/etc/docker/registry/config.yml

if [ -n "$PROXY_REMOTE_URL" -a `grep -c "$PROXY_REMOTE_URL" $CONFIG_YML` -eq 0 ]; then
    echo "proxy:" >> $CONFIG_YML
    echo "  remoteurl: $PROXY_REMOTE_URL" >> $CONFIG_YML
    echo "  username: $PROXY_USERNAME" >> $CONFIG_YML
    echo "  password: $PROXY_PASSWORD" >> $CONFIG_YML
    echo "------ Enabled proxy to remote: $PROXY_REMOTE_URL ------"
elif [ $DELETE_ENABLED = true -a `grep -c "delete:" $CONFIG_YML` -eq 0 ]; then
    sed -i '/rootdirectory/a\  delete:' $CONFIG_YML
    sed -i '/delete/a\    enabled: true' $CONFIG_YML
    echo "------ Enabled local storage delete -----"
fi

sed -i "/headers/a\    Access-Control-Allow-Origin: ['*']" $CONFIG_YML
sed -i "/headers/a\    Access-Control-Allow-Methods: ['HEAD', 'GET', 'OPTIONS', 'DELETE']" $CONFIG_YML
sed -i "/headers/a\    Access-Control-Expose-Headers: ['Docker-Content-Digest']" $CONFIG_YML

case "$1" in
    *.yaml|*.yml) set -- registry serve "$@" ;;
    serve|garbage-collect|help|-*) set -- registry "$@" ;;
esac

exec "$@"

{{< /expand >}}

4. 啟動快取服務

構建好 Docker 映象之後,就可以啟動服務了。如果你不想自己構建,可以直接用我的映象:yangchuansheng/registry-proxy

一般來說,即使你要同時快取 docker.iogcr.iok8s.gcr.ioquay.ioghcr.io,一臺 1C 2G 的雲主機也足夠了(前提是你不在上面跑其他的服務)。我的部落格、評論服務和其他一堆亂七八糟的服務都要跑在雲主機上,所以一臺是不滿足我的需求的,我直接買了兩臺騰訊雲香港輕量級伺服器。

既然買了兩臺,肯定得組個 k3s 叢集啦,看主機名就知道我是用來幹啥的。其中 2C 4G 作為 master 節點,1C 2G 作為 node 節點。

docker.io 為例,建立資源清單:

{{< expand "dockerhub.yaml" >}}

apiVersion: apps/v1
kind: Deployment
metadata:
  name: dockerhub
  labels:
    app: dockerhub
spec:
  replicas: 1
  selector:
    matchLabels:
      app: dockerhub
  template:
    metadata:
      labels:
        app: dockerhub
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - dockerhub
              topologyKey: kubernetes.io/hostname
            weight: 1
      dnsPolicy: None
      dnsConfig:
        nameservers:
          - 8.8.8.8
          - 8.8.4.4
      containers:
      - name: dockerhub
        image: yangchuansheng/registry-proxy:latest
        env:
        - name: PROXY_REMOTE_URL
          value: https://registry-1.docker.io
        - name: PROXY_USERNAME
          value: yangchuansheng
        - name: PROXY_PASSWORD
          value: ********
        ports:
        - containerPort: 5000
          protocol: TCP
        volumeMounts:
        - mountPath: /etc/localtime
          name: localtime
        - mountPath: /var/lib/registry
          name: registry
      volumes:
      - name: localtime
        hostPath:
          path: /etc/localtime
      - name: registry
        hostPath:
          path: /var/lib/registry
---
apiVersion: v1
kind: Service
metadata:
  name: dockerhub
  labels:
    app: dockerhub
spec:
  selector:
    app: dockerhub
  ports:
    - protocol: TCP
      name: http
      port: 5000
      targetPort: 5000

{{< /expand >}}

使用資源清單建立對應的服務:

?  → kubectl apply -f dockerhub.yaml

如果你只有一臺主機,可以使用 docker-compose 來編排容器,配置檔案可以自己參考 k8s 的配置修改,本文就不贅述了。

5. 代理選擇

如果只快取 docker.io,可以直接將 registry-proxy 的埠改成 443,並新增 SSL 證書配置。如果要快取多個公共映象倉庫,就不太推薦這麼做了,因為 443 埠只有一個,多個 registry-proxy 服務不能共用一個埠,合理的做法是使用邊緣代理服務根據域名來轉發請求到不同的 registry-proxy 服務。

對於 Kubernetes 叢集來說,Ingress Controller 即邊緣代理,常見的 Ingress Controller 基本上都是由 Nginx 或者 Envoy 來實現。Envoy 雖為代理界新秀,但生而逢時,它的很多特性都是原生為雲準備的,是真正意義上的 Cloud Native L7 代理和通訊匯流排。比如它的服務發現和動態配置功能,與 Nginx 等代理的熱載入不同,Envoy 可以通過 API 來實現其控制平面,控制平面可以集中服務發現,並通過 API 介面動態更新資料平面的配置,不需要重啟資料平面的代理。不僅如此,控制平面還可以通過 API 將配置進行分層,然後逐層更新。

目前使用 Envoy 實現的 Ingress Controller 有 ContourAmbassadorGloo 等,如果你對 Envoy 比較感興趣,並且想使用 Ingress Controller 作為邊緣代理,可以試試 Contour。Ingress Controller 對底層做了抽象,遮蔽了很多細節,無法顧及到所有細節的配置,必然不會支援底層代理所有的配置項,所以我選擇使用原生的 Envoy 來作為邊緣代理。如果你是單機跑的 registry-proxy 服務,也可以試試 Envoy

6. 代理配置

首先建立 Envoy 的資源清單:

{{< expand "envoy.yaml" >}}

apiVersion: apps/v1
kind: Deployment
metadata:
  name: envoy
  namespace: kube-system
  labels:
    app: envoy
spec:
  replicas: 2
  selector:
    matchLabels:
      app: envoy
  strategy:
    rollingUpdate:
      maxSurge: 0
      maxUnavailable: 1
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: envoy
    spec:
      hostNetwork: true
      dnsPolicy: ClusterFirstWithHostNet
      containers:
      - name: envoy
        image: envoyproxy/envoy:v1.17-latest
        imagePullPolicy: IfNotPresent
        command:
        - envoy
        - /etc/envoy/envoy.yaml
        ports:
        - containerPort: 443
          name: https
        - containerPort: 80
          name: http
        - containerPort: 15001
          name: http-metrics
        volumeMounts:
        - mountPath: /etc/localtime
          name: localtime
        - mountPath: /etc/envoy
          name: envoy
        - mountPath: /root/.acme.sh/fuckcloudnative.io
          name: ssl
      volumes:
      - name: localtime
        hostPath:
          path: /etc/localtime
      - name: ssl
        hostPath:
          path: /root/.acme.sh/fuckcloudnative.io
      - name: envoy
        hostPath:
          path: /etc/envoy

{{< /expand >}}

使用資源清單建立對應的服務:

?  → kubectl apply -f envoy.yaml

這裡選擇使用 hostPath 將 envoy 的配置掛載到容器中,然後通過檔案來動態更新配置。來看下 Envoy 的配置,先進入 /etc/envoy 目錄。

bootstrap 配置:

{{< expand "envoy.yaml" >}}

node:
  id: node0
  cluster: cluster0
dynamic_resources:
  lds_config:
    path: /etc/envoy/lds.yaml
  cds_config:
    path: /etc/envoy/cds.yaml
admin:
  access_log_path: "/dev/stdout"
  address:
    socket_address:
      address: "0.0.0.0"
      port_value: 15001

{{< /expand >}}

LDS 的配置:

{{< expand "lds.yaml" >}}

version_info: "0"
resources:
- "@type": type.googleapis.com/envoy.config.listener.v3.Listener
  name: listener_http
  address:
    socket_address:
      address: 0.0.0.0
      port_value: 80
  filter_chains:
  - filters:
    - name: envoy.filters.network.http_connection_manager
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
        stat_prefix: ingress_http
        codec_type: AUTO
        access_log:
          name: envoy.access_loggers.file
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
            path: /dev/stdout
        route_config:
          name: http_route
          virtual_hosts:
          - name: default
            domains:
            - "*"
            routes:
            - match:
                prefix: "/"
              redirect:
                https_redirect: true
                port_redirect: 443
                response_code: "FOUND"
        http_filters:
        - name: envoy.filters.http.router
- "@type": type.googleapis.com/envoy.config.listener.v3.Listener
  name: listener_https
  address:
    socket_address:
      address: 0.0.0.0
      port_value: 443
  listener_filters:
  - name: "envoy.filters.listener.tls_inspector"
    typed_config: {}
  filter_chains:
  - transport_socket:
      name: envoy.transport_sockets.tls
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
        common_tls_context:
          alpn_protocols: h2,http/1.1
          tls_certificates:
          - certificate_chain:
              filename: "/root/.acme.sh/fuckcloudnative.io/fullchain.cer"
            private_key:
              filename: "/root/.acme.sh/fuckcloudnative.io/fuckcloudnative.io.key"
    filters:
    - name: envoy.filters.network.http_connection_manager
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
        stat_prefix: ingress_https
        codec_type: AUTO
        use_remote_address: true
        access_log:
          name: envoy.access_loggers.file
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
            path: /dev/stdout
        route_config:
          name: https_route
          response_headers_to_add:
          - header:
              key: Strict-Transport-Security
              value: "max-age=15552000; includeSubdomains; preload"
          virtual_hosts:
          - name: docker
            domains:
            - docker.fuckcloudnative.io
            routes:
            - match:
                prefix: "/"
              route:
                cluster: dockerhub
                timeout: 600s
        http_filters:
        - name: envoy.filters.http.router
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router 

{{< /expand >}}

CDS 的配置:

{{< expand "cds.yaml" >}}

version_info: "0"
resources:
- "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
  name: dockerhub
  connect_timeout: 15s
  type: strict_dns
  dns_lookup_family: V4_ONLY
  lb_policy: ROUND_ROBIN
  load_assignment:
    cluster_name: dockerhub
    endpoints:
    - lb_endpoints:
      - endpoint:
          address:
            socket_address:
              address: dockerhub.default
              port_value: 5000

{{< /expand >}}

這裡的 address 使用的是 Kubernetes 叢集內部域名,其他部署方式請自己斟酌。

配置好了 Envoy 之後,就可以通過代理伺服器拉取 docker.io 的映象了。

7. 驗證加速效果

現在你就可以通過代理伺服器來拉取公共映象了。比如你想拉取 nginx:alpine 映象,可以使用下面的命令:

?  → docker pull docker.fuckcloudnative.io/library/nginx:alpine

alpine: Pulling from library/nginx
801bfaa63ef2: Pull complete
b1242e25d284: Pull complete
7453d3e6b909: Pull complete
07ce7418c4f8: Pull complete
e295e0624aa3: Pull complete
Digest: sha256:c2ce58e024275728b00a554ac25628af25c54782865b3487b11c21cafb7fabda
Status: Downloaded newer image for docker.fuckcloudnative.io/library/nginx:alpine
docker.fuckcloudnative.io/library/nginx:alpine

8. 快取所有映象倉庫

前面的示例只是快取了 docker.io,如果要快取所有的公共映象倉庫,可以參考 4-6 節的內容。以 k8s.gcr.io 為例,先準備一個資源清單:

{{< expand "gcr-k8s.yaml" >}}

apiVersion: apps/v1
kind: Deployment
metadata:
  name: gcr-k8s
  labels:
    app: gcr-k8s
spec:
  replicas: 1
  selector:
    matchLabels:
      app: gcr-k8s
  template:
    metadata:
      labels:
        app: gcr-k8s
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - gcr-k8s
              topologyKey: kubernetes.io/hostname
            weight: 1
      dnsPolicy: None
      dnsConfig:
        nameservers:
          - 8.8.8.8
          - 8.8.4.4
      containers:
      - name: gcr-k8s
        image: yangchuansheng/registry-proxy:latest
        env:
        - name: PROXY_REMOTE_URL
          value: https://k8s.gcr.io
        ports:
        - containerPort: 5000
          protocol: TCP
        volumeMounts:
        - mountPath: /etc/localtime
          name: localtime
        - mountPath: /var/lib/registry
          name: registry
      volumes:
      - name: localtime
        hostPath:
          path: /etc/localtime
      - name: registry
        hostPath:
          path: /var/lib/registry
---
apiVersion: v1
kind: Service
metadata:
  name: gcr-k8s
  labels:
    app: gcr-k8s
spec:
  selector:
    app: gcr-k8s
  ports:
    - protocol: TCP
      name: http
      port: 5000
      targetPort: 5000

{{< /expand >}}

將其部署到 Kubernetes 叢集中:

?  → kubectl apply -f gcr-k8s.yaml

lds.yaml 中新增相關配置:

          virtual_hosts:
          - name: docker
            ...
            ...
          - name: k8s
            domains:
            - k8s.fuckcloudnative.io
            routes:
            - match:
                prefix: "/"
              route:
                cluster: gcr-k8s
                timeout: 600s

cds.yaml 中新增相關配置:

- "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
  name: gcr-k8s
  connect_timeout: 1s
  type: strict_dns
  dns_lookup_family: V4_ONLY
  lb_policy: ROUND_ROBIN
  load_assignment:
    cluster_name: gcr-k8s
    endpoints:
    - lb_endpoints:
      - endpoint:
          address:
            socket_address:
              address: gcr-k8s.default
              port_value: 5000

其他映象倉庫可照搬上述步驟,以下是我自己跑的所有快取服務容器:

?  → kubectl get pod -o wide

gcr-8647ffb586-67c6g                     1/1     Running   0          21h     10.42.1.52    blog-k3s02
ghcr-7765f6788b-hxxvc                    1/1     Running   0          21h     10.42.1.55    blog-k3s01
dockerhub-94bbb7497-x4zwg                1/1     Running   0          21h     10.42.1.54    blog-k3s02
gcr-k8s-644db84879-7xssb                 1/1     Running   0          21h     10.42.1.53    blog-k3s01
quay-559b65848b-ljclb                    1/1     Running   0          21h     10.42.0.154   blog-k3s01

9. 容器執行時配置

配置好所有的快取服務後,就可以通過代理來拉取公共映象了,只需按照下面的列表替換映象地址中的欄位就行了:

原 URL 替換後的 URL
docker.io/xxx/xxx 或 xxx/xxx docker.fuckcloudnative.io/xxx/xxx
docker.io/library/xxx 或 xxx docker.fuckcloudnative.io/library/xxx
gcr.io/xxx/xxx gcr.fuckcloudnative.io/xxx/xxx
k8s.gcr.io/xxx/xxx k8s.fuckcloudnative.io/xxx/xxx
quay.io/xxx/xxx quay.fuckcloudnative.io/xxx/xxx
ghcr.io/xxx/xxx ghcr.fuckcloudnative.io/xxx/xxx

當然,最好的方式還是直接配置 registry mirror,Docker 只支援配置 docker.io 的 registry mirror,ContainerdPodman 支援配置所有映象倉庫的 registry mirror。

Docker

Docker 可以修改配置檔案 /etc/docker/daemon.json,新增下面的內容:

{
    "registry-mirrors": [
        "https://docker.fuckcloudnative.io"
    ]
}

然後重啟 Docker 服務,就可以直接拉取 docker.io 的映象了,不需要顯示指定代理伺服器的地址,Docker 服務本身會自動通過代理伺服器去拉取映象。比如:

? → docker pull nginx:alpine
? → docker pull docker.io/library/nginx:alpine

Containerd

Containerd 就比較簡單了,它支援任意 registry 的 mirror,只需要修改配置檔案 /etc/containerd/config.toml,新增如下的配置:

    [plugins."io.containerd.grpc.v1.cri".registry]
      [plugins."io.containerd.grpc.v1.cri".registry.mirrors]
        [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
          endpoint = ["https://docker.fuckcloudnative.io"]
        [plugins."io.containerd.grpc.v1.cri".registry.mirrors."k8s.gcr.io"]
          endpoint = ["https://k8s.fuckcloudnative.io"]
        [plugins."io.containerd.grpc.v1.cri".registry.mirrors."gcr.io"]
          endpoint = ["https://gcr.fuckcloudnative.io"]
        [plugins."io.containerd.grpc.v1.cri".registry.mirrors."ghcr.io"]
          endpoint = ["https://ghcr.fuckcloudnative.io"]
        [plugins."io.containerd.grpc.v1.cri".registry.mirrors."quay.io"]
          endpoint = ["https://quay.fuckcloudnative.io"]

重啟 Containerd 服務後,就可以直接拉取所有映象了,不需要修改任何字首,Containerd 會根據配置自動選擇相應的代理 URL 拉取映象。

Podman

Podman 也支援任意 registry 的 mirror,只需要修改配置檔案 /etc/containers/registries.conf,新增如下的配置:

unqualified-search-registries = ['docker.io', 'k8s.gcr.io', 'gcr.io', 'ghcr.io', 'quay.io']

[[registry]]
prefix = "docker.io"
insecure = true
location = "registry-1.docker.io"

[[registry.mirror]]
location = "docker.fuckcloudnative.io"

[[registry]]
prefix = "k8s.gcr.io"
insecure = true
location = "k8s.gcr.io"

[[registry.mirror]]
location = "k8s.fuckcloudnative.io"

[[registry]]
prefix = "gcr.io"
insecure = true
location = "gcr.io"

[[registry.mirror]]
location = "gcr.fuckcloudnative.io"

[[registry]]
prefix = "ghcr.io"
insecure = true
location = "ghcr.io"

[[registry.mirror]]
location = "ghcr.fuckcloudnative.io"

[[registry]]
prefix = "quay.io"
insecure = true
location = "quay.io"

[[registry.mirror]]
location = "quay.fuckcloudnative.io"

然後就可以直接拉取所有映象了,不需要修改任何字首,Podman 會根據配置自動選擇相應的代理 URL 拉取映象。而且 Podman 還有 fallback 機制,上面的配置表示先嚐試通過 registry.mirrorlocation 欄位的 URL 來拉取映象,如果失敗就會嘗試通過 registry 中 location 欄位的 URL 來拉取。

10. 清理快取

快取服務會將拉取的映象快取到本地,所以需要消耗磁碟容量。一般雲主機的磁碟容量都不是很大,OSS 和 s3 儲存都比較貴,不太划算。

為了解決這個問題,我推薦定期刪除快取到本地磁碟的部分映象,或者刪除所有映象。方法也比較簡單,單獨再部署一個 registry,共用其他 registry 的儲存,並啟用 delete 功能,然後再通過 API 或者 Dashboard 進行刪除。

先準備一個資源清單:

{{< expand "reg-local.yaml" >}}

apiVersion: apps/v1
kind: Deployment
metadata:
  name: reg-local
  labels:
    app: reg-local
spec:
  replicas: 1
  selector:
    matchLabels:
      app: reg-local
  template:
    metadata:
      labels:
        app: reg-local
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - reg-local
              topologyKey: kubernetes.io/hostname
            weight: 1
      containers:
      - name: reg-local
        image: yangchuansheng/registry-proxy:latest
        env:
        - name: DELETE_ENABLED
          value: "true"
        ports:
        - containerPort: 5000
          protocol: TCP
        volumeMounts:
        - mountPath: /etc/localtime
          name: localtime
        - mountPath: /var/lib/registry
          name: registry
      volumes:
      - name: localtime
        hostPath:
          path: /etc/localtime
      - name: registry
        hostPath:
          path: /var/lib/registry
---
apiVersion: v1
kind: Service
metadata:
  name: reg-local
  labels:
    app: reg-local
spec:
  selector:
    app: reg-local
  ports:
    - protocol: TCP
      name: http
      port: 5000
      targetPort: 5000

{{< /expand >}}

將其部署到 Kubernetes 叢集中:

?  → kubectl apply -f reg-local.yaml

再準備一個 Docker Registry UI 的資源清單:

{{< expand "registry-ui.yaml" >}}

apiVersion: apps/v1
kind: Deployment
metadata:
  name: registry-ui
  labels:
    app: registry-ui
spec:
  replicas: 1
  selector:
    matchLabels:
      app: registry-ui
  template:
    metadata:
      labels:
        app: registry-ui
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - registry-ui
              topologyKey: kubernetes.io/hostname
            weight: 1
      tolerations:
      - key: node-role.kubernetes.io/ingress
        operator: Exists
        effect: NoSchedule
      containers:
      - name: registry-ui
        image: joxit/docker-registry-ui:static
        env:
        - name: REGISTRY_TITLE
          value: My Private Docker Registry
        - name: REGISTRY_URL
          value: "http://reg-local:5000"
        - name: DELETE_IMAGES
          value: "true"
        ports:
        - containerPort: 80
          protocol: TCP
        volumeMounts:
        - mountPath: /etc/localtime
          name: localtime
      volumes:
      - name: localtime
        hostPath:
          path: /etc/localtime
---
apiVersion: v1
kind: Service
metadata:
  name: registry-ui
  labels:
    app: registry-ui
spec:
  selector:
    app: registry-ui
  ports:
    - protocol: TCP
      name: http
      port: 80
      targetPort: 80

{{< /expand >}}

將其部署到 Kubernetes 叢集中:

?  → kubectl apply -f registry-ui.yaml

這樣就可以通過 Dashboard 來清理映象釋放空間了。

或者直接簡單粗暴,定時刪除整個儲存目錄的內容。例如,執行命令 crontab -e,新增如下內容:

* * */2 * * /usr/bin/rm -rf /var/lib/registry/* &>/dev/null

表示每過兩天清理一次 /var/lib/registry/ 目錄。

11. 防白嫖認證

最後還有一個問題,我把快取服務的域名全部公開了,如果大家都來白嫖,我的雲主機肯定承受不住。為了防止白嫖,我得給 registry-proxy 加個認證,最簡單的方法就是使用 basic auth,用 htpasswd 來儲存密碼。

  1. 為使用者 admin 建立一個密碼檔案,密碼為 admin

    ? → docker run \
      --entrypoint htpasswd \
      registry:2.6 -Bbn admin admin > htpasswd
    
  2. 建立 Secret:

    ? → kubectl create secret generic registry-auth --from-file=htpasswd
    
  3. 修改資源清單的配置,以 docker.io 為例:

    {{< expand "dockerhub.yaml" >}}

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: dockerhub
      labels:
        app: dockerhub
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: dockerhub
      template:
        metadata:
          labels:
            app: dockerhub
        spec:
          affinity:
            podAntiAffinity:
              preferredDuringSchedulingIgnoredDuringExecution:
              - podAffinityTerm:
                  labelSelector:
                    matchExpressions:
                    - key: app
                      operator: In
                      values:
                      - dockerhub
                  topologyKey: kubernetes.io/hostname
                weight: 1
          dnsPolicy: None
          dnsConfig:
            nameservers:
              - 8.8.8.8
              - 8.8.4.4
          containers:
          - name: dockerhub
            image: yangchuansheng/registry-proxy:latest
            env:
            - name: PROXY_REMOTE_URL
              value: https://registry-1.docker.io
            - name: PROXY_USERNAME
              value: yangchuansheng
            - name: PROXY_PASSWORD
              value: ********
    +       - name: REGISTRY_AUTH_HTPASSWD_REALM
    +         value: Registry Realm
    +       - name: REGISTRY_AUTH_HTPASSWD_PATH
    +         value: /auth/htpasswd 
            ports:
            - containerPort: 5000
              protocol: TCP
            volumeMounts:
            - mountPath: /etc/localtime
              name: localtime
            - mountPath: /var/lib/registry
              name: registry
    +       - mountPath: /auth
    +         name: auth
          volumes:
          - name: localtime
            hostPath:
              path: /etc/localtime
          - name: registry
            hostPath:
              path: /var/lib/registry
    +     - name: auth
    +       secret:
    +         secretName: registry-auth
    

    {{< /expand >}}

    apply 使其生效:

    ? → kubectl apply -f dockerhub.yaml
    
  4. 嘗試拉取映象:

    ? → docker pull docker.fuckcloudnative.io/library/nginx:latest
    
    Error response from daemon: Get https://docker.fuckcloudnative.io/v2/library/nginx/manifests/latest: no basic auth credentials
    
  5. 登入映象倉庫:

    ? → docker login docker.fuckcloudnative.io
    Username: admin
    Password:
    WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
    Configure a credential helper to remove this warning. See
    https://docs.docker.com/engine/reference/commandline/login/#credentials-store
    
    Login Succeeded
    

    現在就可以正常拉取映象了。

如果你想更細粒度地控制許可權,可以使用 Token 的方式來進行認證,具體可以參考 docker_auth 這個專案。

12. 費用評估

好了,現在我們來評估一下這一切的費用。首先你得有一個會魔法的伺服器,國內的肯定不用考慮了,必須選擇國外的,而且到國內的速度還過得去的,最低最低不會低於 30 人民幣/月 吧。除此之外,你還得擁有一個個人域名,這個價格不好說,總而言之,加起來肯定不會低於 30 吧,多數人肯定是下不去這個手的。沒關係,我有一個更便宜的方案,我已經部署好了一切,你可以直接用我的服務,當然我也是自己買的伺服器,每個月也是要花錢的,如果你真的想用,只需要每月支付 3 元,以此來保障我每個月的伺服器費用。當然肯定不止你一個人,目前大概有十幾個使用者,後面如果人數特別多,再考慮加伺服器。這個需要你自己考慮清楚,有意者掃描下方的二維碼向我諮詢:

Docker 映象加速教程


Kubernetes 1.18.2 1.17.5 1.16.9 1.15.12離線安裝包釋出地址http://store.lameleg.com ,歡迎體驗。 使用了最新的sealos v3.3.6版本。 作了主機名解析配置優化,lvscare 掛載/lib/module解決開機啟動ipvs載入問題, 修復lvscare社群netlink與3.10核心不相容問題,sealos生成百年證書等特性。更多特性 https://github.com/fanux/sealos 。歡迎掃描下方的二維碼加入釘釘群 ,釘釘群已經整合sealos的機器人實時可以看到sealos的動態。

相關文章