新聞中心
徹底解決 Gcr、Quay、dockerHub 鏡像下載難題!
作者:米開朗基楊 2021-02-18 08:22:26
云計算 國內(nèi)其他的鏡像加速方案大多都是采用定時同步的方式來緩存,這種方法是有一定延遲的,不能保證及時更新,ustc 和七牛云等鏡像加速器我都試過了,非常不靠譜,很多鏡像都沒有。

目前創(chuàng)新互聯(lián)已為1000多家的企業(yè)提供了網(wǎng)站建設(shè)、域名、虛擬主機、網(wǎng)站托管、服務(wù)器托管、企業(yè)網(wǎng)站設(shè)計、惠山網(wǎng)站維護等服務(wù),公司將堅持客戶導(dǎo)向、應(yīng)用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長,共同發(fā)展。
本文轉(zhuǎn)載自微信公眾號「云原生實驗室」,作者米開朗基楊。轉(zhuǎn)載本文請聯(lián)系云原生實驗室公眾號。
前言
在使用 Docker 和 Kubernetes 時,我們經(jīng)常需要訪問 gcr.io 和 quay.io 鏡像倉庫,由于眾所周知的原因,這些鏡像倉庫在中國都無法訪問,唯一能訪問的是 Docker Hub,但速度也是奇慢無比。gcr.azk8s.cn 是 gcr.io 鏡像倉庫的代理站點,原來可以通過 gcr.azk8s.cn 訪問 gcr.io 倉庫里的鏡像,但是目前 *.azk8s.cn 已經(jīng)僅限于 Azure 中國的 IP 使用,不再對外提供服務(wù)了。國內(nèi)其他的鏡像加速方案大多都是采用定時同步的方式來緩存,這種方法是有一定延遲的,不能保證及時更新,ustc 和七牛云等鏡像加速器我都試過了,非常不靠譜,很多鏡像都沒有。
為了能夠順利訪問 gcr.io 等鏡像倉庫,我們需要在墻外自己搭建一個類似于 gcr.azk8s.cn 的鏡像倉庫代理站點。利用 Docker 的開源項目 registry[1] 就可以實現(xiàn)這個需求,registry 不僅可以作為本地私有鏡像倉庫,還可以作為上游鏡像倉庫的緩存,也就是 pull through cache。
先來感受下速度:
1. 前提條件
一臺能夠施展魔法的服務(wù)器(你懂得,可以直接訪問 gcr.io)
一個域名和域名相關(guān)的 SSL 證書(docker pull 鏡像時需要驗證域名證書),一般用 Let's Encrypt[2] 就夠了。
2. 核心思路
registry 可以通過設(shè)置參數(shù) remoteurl 將其作為遠端倉庫的緩存?zhèn)}庫,這樣當你通過這個私有倉庫的地址拉取鏡像時,regiistry 會先將鏡像緩存到本地存儲,然后再提供給拉取的客戶端(有可能這兩個步驟是同時的,我也不太清楚)。我們可以先部署一個私有 registry,然后將 remoteurl 設(shè)為需要加速的鏡像倉庫地址,基本上就可以了。
3. 定制 registry
為了能夠支持緩存 docker.io、gcr.io、k8s.gcr.io、quay.io 和 ghcr.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 用來將環(huán)境變量傳入配置文件:
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 "$@"
4. 啟動緩存服務(wù)
構(gòu)建好 Docker 鏡像之后,就可以啟動服務(wù)了。如果你不想自己構(gòu)建,可以直接用我的鏡像:yangchuansheng/registry-proxy。
一般來說,即使你要同時緩存 docker.io、gcr.io、k8s.gcr.io、quay.io 和 ghcr.io,一臺 1C 2G 的云主機也足夠了(前提是你不在上面跑其他的服務(wù))。我的博客、評論服務(wù)和其他一堆亂七八糟的服務(wù)都要跑在云主機上,所以一臺是不滿足我的需求的,我直接買了兩臺騰訊云香港輕量級服務(wù)器。
既然買了兩臺,肯定得組個 k3s 集群啦,看主機名就知道我是用來干啥的。其中 2C 4G 作為 master 節(jié)點,1C 2G 作為 node 節(jié)點。
以 docker.io 為例,創(chuàng)建資源清單:
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
使用資源清單創(chuàng)建對應(yīng)的服務(wù):
- → kubectl apply -f dockerhub.yaml
如果你只有一臺主機,可以使用 docker-compose 來編排容器,配置文件可以自己參考 k8s 的配置修改,本文就不贅述了。
5. 代理選擇
如果只緩存 docker.io,可以直接將 registry-proxy 的端口改成 443,并添加 SSL 證書配置。如果要緩存多個公共鏡像倉庫,就不太推薦這么做了,因為 443 端口只有一個,多個 registry-proxy 服務(wù)不能共用一個端口,合理的做法是使用邊緣代理服務(wù)根據(jù)域名來轉(zhuǎn)發(fā)請求到不同的 registry-proxy 服務(wù)。
對于 Kubernetes 集群來說,Ingress Controller 即邊緣代理,常見的 Ingress Controller 基本上都是由 Nginx 或者 Envoy 來實現(xiàn)。Envoy 雖為代理界新秀,但生而逢時,它的很多特性都是原生為云準備的,是真正意義上的 Cloud Native L7 代理和通信總線。比如它的服務(wù)發(fā)現(xiàn)和動態(tài)配置功能,與 Nginx 等代理的熱加載不同,Envoy 可以通過 API 來實現(xiàn)其控制平面,控制平面可以集中服務(wù)發(fā)現(xiàn),并通過 API 接口動態(tài)更新數(shù)據(jù)平面的配置,不需要重啟數(shù)據(jù)平面的代理。不僅如此,控制平面還可以通過 API 將配置進行分層,然后逐層更新。
目前使用 Envoy 實現(xiàn)的 Ingress Controller 有 ??Contour 和 Gloo[3] 等,如果你對 Envoy 比較感興趣,并且想使用 Ingress Controller 作為邊緣代理,可以試試 ??Contour。Ingress Controller 對底層做了抽象,屏蔽了很多細節(jié),無法顧及到所有細節(jié)的配置,必然不會支持底層代理所有的配置項,所以我選擇使用原生的 Envoy 來作為邊緣代理。如果你是單機跑的 registry-proxy 服務(wù),也可以試試 Envoy。
6. 代理配置
首先創(chuàng)建 Envoy 的資源清單:
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
使用資源清單創(chuàng)建對應(yīng)的服務(wù):
- → kubectl apply -f envoy.yaml
這里選擇使用 hostPath 將 envoy 的配置掛載到容器中,然后??通過文件來動態(tài)更新配置。來看下 Envoy 的配置,先進入 /etc/envoy 目錄。
bootstrap 配置:
- 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
LDS 的配置:
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
CDS 的配置:
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
這里的 address 使用的是 Kubernetes 集群內(nèi)部域名,其他部署方式請自己斟酌。
配置好了 Envoy 之后,就可以通過代理服務(wù)器拉取 docker.io 的鏡像了。
7. 驗證加速效果
現(xiàn)在你就可以通過代理服務(wù)器來拉取公共鏡像了。比如你想拉取 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 節(jié)的內(nèi)容。以 k8s.gcr.io 為例,先準備一個資源清單:
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
將其部署到 Kubernetes 集群中:
- → kubectl apply -f gcr-k8s.yaml
在 lds.yaml 中添加相關(guān)配置:
- virtual_hosts:
- - name: docker
- ...
- ...
- - name: k8s
- domains:
- - k8s.fuckcloudnative.io
- routes:
- - match:
- prefix: "/"
- route:
- cluster: gcr-k8s
- timeout: 600s
在 cds.yaml 中添加相關(guān)配置:
- - "@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
其他鏡像倉庫可照搬上述步驟,以下是我自己跑的所有緩存服務(wù)容器:
- → 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. 容器運行時配置
配置好所有的緩存服務(wù)后,就可以通過代理來拉取公共鏡像了,只需按照下面的列表替換鏡像地址中的字段就行了:
| 原 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,Containerd 和 Podman 支持配置所有鏡像倉庫的 registry mirror。
Docker
Docker 可以修改配置文件 /etc/docker/daemon.json,添加下面的內(nèi)容:
- {
- "registry-mirrors": [
- "https://docker.fuckcloudnative.io"
- ]
- }
然后重啟 Docker 服務(wù),就可以直接拉取 docker.io 的鏡像了,不需要顯示指定代理服務(wù)器的地址,Docker 服務(wù)本身會自動通過代理服務(wù)器去拉取鏡像。比如:
- → 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 服務(wù)后,就可以直接拉取所有鏡像了,不需要修改任何前綴,Containerd 會根據(jù)配置自動選擇相應(yīng)的代理 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 會根據(jù)配置自動選擇相應(yīng)的代理 URL 拉取鏡像。而且 Podman 還有 fallback 機制,上面的配置表示先嘗試通過 registry.mirror 中 location 字段的 URL 來拉取鏡像,如果失敗就會嘗試通過 registry 中 location 字段的 URL 來拉取。
10. 清理緩存
緩存服務(wù)會將拉取的鏡像緩存到本地,所以需要消耗磁盤容量。一般云主機的磁盤容量都不是很大,OSS 和 s3 存儲都比較貴,不太劃算。
為了解決這個問題,我推薦定期刪除緩存到本地磁盤的部分鏡像,或者刪除所有鏡像。方法也比較簡單,單獨再部署一個 registry,共用其他 registry 的存儲,并啟用 delete 功能,然后再通過 API 或者 Dashboard 進行刪除。
先準備一個資源清單:
- 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:
-  
網(wǎng)頁名稱:徹底解決Gcr、Quay、DockerHub鏡像下載難題!
分享網(wǎng)址:http://fisionsoft.com.cn/article/djoojhj.html


咨詢
建站咨詢
