Docker 安装与配置

前置条件

由于 Docker 运行于 Linux 内核中,因此对于系统要求如下:

  • 系统必须为 64 位操作系统。
  • 系统内核版本需要大于 3.10。
  • 硬盘空间推荐为 100 GB 以上。

可以使用以下命令查询系统位数和内核版本:

[root@server3 ~]$ uname -r
3.10.0-1160.42.2.el7.x86_64

Docker 引擎分为两个版本,社区版本(CE,Community Edition)和企业版本(EE,Enterprise Edition)。通常安装社区稳定版。

安装 Docker

对于之前安装过 Docker 的主机,需要先卸载干净后再进行安装。

在全新安装时,需要先修改仓库配置,增加 Docker CE 的安装源:

[root@server4 ~]$ yum install -y yum-utils
[root@server4 ~]$ yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
Loaded plugins: fastestmirror
adding repo from: https://download.docker.com/linux/centos/docker-ce.repo
grabbing file https://download.docker.com/linux/centos/docker-ce.repo to /etc/yum.repos.d/docker-ce.repo
repo saved to /etc/yum.repos.d/docker-ce.repo

如果要安装 Nightly 或 Test 版本,可以使用以下命令启用相应的仓库。要关闭,使用 --disable 参数。

[root@server4 ~]$ yum-config-manager --enable docker-ce-nightly
[root@server4 ~]$ yum-config-manager --enable docker-ce-test

安装最新版 Docker CE:

[root@server4 ~]$ yum install -y docker-ce docker-ce-cli containerd.io

要安装特定版本,可以使用 yum list 命令进行查询:

[root@server4 ~]$ yum list docker-ce --showduplicates | sort -r
docker-ce.x86_64            3:20.10.9-3.el7                    docker-ce-stable 
docker-ce.x86_64            3:20.10.9-3.el7                    @docker-ce-stable
docker-ce.x86_64            3:20.10.8-3.el7                    docker-ce-stable 

例如,安装 20.10.10 版本:

[root@server4 ~]$ yum install -y docker-ce-20.10.10 docker-ce-cli-20.10.10 containerd.io

Docker 安装完成后会创建一个 Docker 组,可以将普通用户加入该组,这样普通用户就可以直接运行 Docker 命令:

[root@server4 ~]$ useradd -p user10 user10
[root@server4 ~]$ su -l user10
[user10@server4 ~]$ docker image ls
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/images/json": dial unix /var/run/docker.sock: connect: permission denied
[user10@server4 ~]$ su -c 'usermod -G docker user10'
[user10@server4 ~]$ docker image ls
REPOSITORY    TAG       IMAGE ID       CREATED       SIZE
hello-world   latest    feb5d9fea6a5   3 weeks ago   13.3kB

更新 Docker

在 Docker 仓库源已启用的情况下,可以使用 yum upgrade 命令来更新 Docker:

[root@server4 ~]$ yum upgrade

卸载 Docker

使用以下命令完全卸载旧版本的 Docker 及相关程序:

[root@server4 ~]$ yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine

删除新版 Docker 引擎:

[user10@server4 ~]$ yum remove docker-ce docker-ce-cli containerd.io

删除 Docker 使用的库、镜像和容器等数据:

[root@server4 ~]$ rm -rf /var/lib/docker
[root@server4 ~]$ rm -rf /var/lib/containerd

启动 Docker

启动服务并设置开机启动:

[root@server4 ~]$ systemctl enable --now docker
Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.

启动之后,可以使用 hello-world 镜像来测试 Docker 功能是否正常:

[root@server4 ~]$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world

查询 Docker 版本

使用 docker version 命令来查询 Docker 服务端和客户端的版本:

[root@server4 ~]$ docker version
Client: Docker Engine - Community
 Version:           20.10.9
 API version:       1.41
 Go version:        go1.16.8
 Git commit:        c2ea9bc
 Built:             Mon Oct  4 16:08:14 2021
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.9
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.16.8
  Git commit:       79ea9d3
  Built:            Mon Oct  4 16:06:37 2021
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.4.11
  GitCommit:        5b46e404f6b9f661a205e28d59c982d3634148f8
 runc:
  Version:          1.0.2
  GitCommit:        v1.0.2-0-g52b36a2
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

查看 Docker 信息

可以使用 docker info 命令来查看 Docker 的运行状态:

[root@server4 ~]$ docker info
Client:
 Context:    default
 Debug Mode: false
 Plugins:
  app: Docker App (Docker Inc., v0.9.1-beta3)
  buildx: Build with BuildKit (Docker Inc., v0.6.3-docker)
  scan: Docker Scan (Docker Inc., v0.8.0)

Server:
 Containers: 1
  Running: 0
  Paused: 0
  Stopped: 1
 Images: 5
 Server Version: 20.10.9
 Storage Driver: overlay2
  Backing Filesystem: xfs
  Supports d_type: true
  Native Overlay Diff: true
  userxattr: false
 Logging Driver: json-file
 Cgroup Driver: cgroupfs
 Cgroup Version: 1
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
 Swarm: inactive
 Runtimes: io.containerd.runc.v2 io.containerd.runtime.v1.linux runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: 5b46e404f6b9f661a205e28d59c982d3634148f8
 runc version: v1.0.2-0-g52b36a2
 init version: de40ad0
 Security Options:
  seccomp
   Profile: default
 Kernel Version: 3.10.0-1160.42.2.el7.x86_64
 Operating System: CentOS Linux 7 (Core)
 OSType: linux
 Architecture: x86_64
 CPUs: 16
 Total Memory: 3.682GiB
 Name: server4
 ID: 7YVL:YG7U:BI3Q:BOVZ:XRWD:7BQO:6H3R:2BUW:OCQO:7JKZ:2OWC:UL2Q
 Docker Root Dir: /var/lib/docker
 Debug Mode: false
 Registry: https://index.docker.io/v1/
 Labels:
 Experimental: false
 Insecure Registries:
  127.0.0.0/8
 Registry Mirrors:
  https://mirror.ccs.tencentyun.com/
 Live Restore Enabled: false

使用 docker system df 命令来查询 Docker 的磁盘空间使用情况:

[root@UAT1542 ~]$ docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          21        8         7.737GB   3.526GB (45%)
Containers      10        9         18.21GB   175B (0%)
Local Volumes   10        3         135.9kB   101.9kB (74%)
Build Cache     0         0         0B        0B

清理数据

使用 docker system prune 命令来安全清理没用到的数据,比如停止状态的容器、没有被使用的中间镜像层、没被使用的 Docker 虚拟网络:

[root@UAT1542 ~]$ docker system prune
WARNING! This will remove:
  - all stopped containers
  - all networks not used by at least one container
  - all dangling images
  - all dangling build cache

Are you sure you want to continue? [y/N] y
Deleted Containers:
5c6cdebbb209507a4886debe278f59cdf1dbebd6a8cfbf3b1ea32828cf3adbfa

Total reclaimed space: 175B

还能加入 -a 参数进一步删除没有被使用的镜像(不包括被停止状态容器使用的镜像):

root@k8s-204:~# docker system prune -a
WARNING! This will remove:
  - all stopped containers
  - all networks not used by at least one container
  - all images without at least one container associated to them
  - all build cache

Are you sure you want to continue? [y/N] 

启动参数

Docker 服务启动实际上是调用了 dockerd 命令,Docker Daemon 常用启动参数如下:

参数 说明
–api-cors-header=“” 设定远程访问 API 所需的 cors-header。
–authorization-plugin=[] 设置认证插件列表。
-b, --bridge=“” 设置容器使用的网桥。
–bip=“” 设定网络桥 IP 和子网掩码。
-D, --debug=false 启动 Debug 模式。
-d, --daemon=false 启动守护进程模式。
–default-gateway=“” 设置容器使用的 IPv4 网关。
–dns=[] 设置容器使用的 DNS 服务器列表。
–default-ulimit=[] 设定容器默认的 ulimit 参数。
-exec-opt=[] 设定管理容器使用的参数。
–exec-root=“/var/run/docker” 设定 exec-driver 默认根目录地址。
–fixed-cidr=“” 设定容器使用的 IPv4 子网段。
-G, --group=“docker” 设定 UNIX Socket 文件用户所属组。
-g, --graph=“/var/lib/docker” 设定 Docker 服务运行时的根目录。
-H, --host=[] 设定 Docker 服务监听地址。
–icc=true 允许容器之间互相通信。
–insecure-registry=[] 设定可信任仓库地址。
–ip=0.0.0.0 设定绑定容器端口信息时的默认 IP。
–ip-forward=true 允许转发 IPv4 数据包。
–ip-masq=true 允许 IP 伪装。
–iptables=true 允许修改 iptables 规则。
–ipv6=true 允许使用 IPv6 网络。
-l, --log-level=“info” 设定日志输出等级。
–label=[] 按照 Key=Value 设定 Daemon 标签。
–log-driver=“json-file” 设定日志驱动格式。
–log-opt=[] 设定日志记录驱动参数。
–mtu=0 设定容器 MTU 值。
-p, --pidfile=“/var/run/docker.pid” 设定 Docker.pid 文件路径。
–registry-mirror=[] 设定优先访问的仓库镜像地址。
-s, --storage-driver=“” 设定数据储存驱动方式。
–selinux-enabled=false 启用或禁用 SELinux
–storage-opt=[] 设置数据存储驱动的参数
–tls=false 启用或禁用 TLS,但不启用客户端认证
–tlscacert=“~/.docker/ca.pem” 设置 CA 证书的位置
–tlscert=“~/.docker/cert.pem” 设置 TLS 证书的位置
–tlskey=“~/.docker/key.pem” 设置 TLS 密钥的位置
–tlsverify=false 启用或禁用 TLS 验证远程访问请求
–userland-proxy=true 启用或禁用用户空间代理
-v, --version=false 输出 Docker 版本信息

如果参数后面有 []标记,表示该参数可以多次使用。例如,要启用 Debug 模式并监听本地 2376 端口:

[root@server4 ~]$ dockerd -D -H tcp:/127.0.0.1:2376

可以将启动参数写入 /etc/docker/daemon.json 文件中,在 dockerd 服务启动时读取该文件。请注意,该配置文件中不能有与启动参数重复的配置项,否则会导致启动错误:

[root@server4 ~]$ vi /etc/docker/daemon.json
{
        "debug": true,
        "hosts": ["tcp://127.0.0.1:2376"]
}

也可以修改 /usr/lib/systemd/system/docker.service 文件中 ExecStart 后面的参数来配置启动参数:

[root@server1 ~]$ vi /usr/lib/systemd/system/docker.service 
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock -H tcp://192.168.2.234:5999
[root@server1 ~]$ systemctl daemon-reload
[root@server1 ~]$ systemctl restart docker

代理服务

由于 Docker Hub 官网已经不能正常访问,因此尝试自建 Docker 镜像代理加速服务(参考 1参考 2参考 3):

  • 首先打开 https://www.cloudflare.com/,左侧导航找到「Workers 和 Pages」,点击「创建」按钮,在「创建应用程序」页面点击「创建 Worker」。

  • 在创建页面输入 Worker 名称 dockerproxy,然后点击「部署」。

  • 部署成功后,点击右上角「编辑代码」,输入 js 内容(来源),其中 workers_url 值为 cf 托管的自有域名:

    // _worker.js
    
    // Docker镜像仓库主机地址
    let hub_host = 'registry-1.docker.io'
    // Docker认证服务器地址
    const auth_url = 'https://auth.docker.io'
    // 自定义的工作服务器地址
    let workers_url = 'https://docker.x2b.net'
    
    let 屏蔽爬虫UA = ['netcraft'];
    
    // 根据主机名选择对应的上游地址
    function routeByHosts(host) {
    		// 定义路由表
    	const routes = {
    		// 生产环境
    		"quay": "quay.io",
    		"gcr": "gcr.io",
    		"k8s-gcr": "k8s.gcr.io",
    		"k8s": "registry.k8s.io",
    		"ghcr": "ghcr.io",
    		"cloudsmith": "docker.cloudsmith.io",
    		
    		// 测试环境
    		"test": "registry-1.docker.io",
    	};
    
    	if (host in routes) return [ routes[host], false ];
    	else return [ hub_host, true ];
    }
    
    /** @type {RequestInit} */
    const PREFLIGHT_INIT = {
    	// 预检请求配置
    	headers: new Headers({
    		'access-control-allow-origin': '*', // 允许所有来源
    		'access-control-allow-methods': 'GET,POST,PUT,PATCH,TRACE,DELETE,HEAD,OPTIONS', // 允许的HTTP方法
    		'access-control-max-age': '1728000', // 预检请求的缓存时间
    	}),
    }
    
    /**
     * 构造响应
     * @param {any} body 响应体
     * @param {number} status 响应状态码
     * @param {Object<string, string>} headers 响应头
     */
    function makeRes(body, status = 200, headers = {}) {
    	headers['access-control-allow-origin'] = '*' // 允许所有来源
    	return new Response(body, { status, headers }) // 返回新构造的响应
    }
    
    /**
     * 构造新的URL对象
     * @param {string} urlStr URL字符串
     */
    function newUrl(urlStr) {
    	try {
    		return new URL(urlStr) // 尝试构造新的URL对象
    	} catch (err) {
    		return null // 构造失败返回null
    	}
    }
    
    function isUUID(uuid) {
    	// 定义一个正则表达式来匹配 UUID 格式
    	const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
    	
    	// 使用正则表达式测试 UUID 字符串
    	return uuidRegex.test(uuid);
    }
    
    async function nginx() {
    	const text = `
    	<!DOCTYPE html>
    	<html>
    	<head>
    	<title>Welcome to nginx!</title>
    	<style>
    		body {
    			width: 35em;
    			margin: 0 auto;
    			font-family: Tahoma, Verdana, Arial, sans-serif;
    		}
    	</style>
    	</head>
    	<body>
    	<h1>Welcome to nginx!</h1>
    	<p>If you see this page, the nginx web server is successfully installed and
    	working. Further configuration is required.</p>
    	
    	<p>For online documentation and support please refer to
    	<a href="http://nginx.org/">nginx.org</a>.<br/>
    	Commercial support is available at
    	<a href="http://nginx.com/">nginx.com</a>.</p>
    	
    	<p><em>Thank you for using nginx.</em></p>
    	</body>
    	</html>
    	`
    	return text ;
    }
    
    export default {
    	async fetch(request, env, ctx) {
    		const getReqHeader = (key) => request.headers.get(key); // 获取请求头
    
    		let url = new URL(request.url); // 解析请求URL
    		const userAgentHeader = request.headers.get('User-Agent');
    		const userAgent = userAgentHeader ? userAgentHeader.toLowerCase() : "null";
    		if (env.UA) 屏蔽爬虫UA = 屏蔽爬虫UA.concat(await ADD(env.UA));
    		workers_url = `https://${url.hostname}`;
    		const pathname = url.pathname;
    		const hostname = url.searchParams.get('hubhost') || url.hostname; 
    		const hostTop = hostname.split('.')[0];// 获取主机名的第一部分
    		const checkHost = routeByHosts(hostTop);
    		hub_host = checkHost[0]; // 获取上游地址
    		const fakePage = checkHost[1];
    		console.log(`域名头部: ${hostTop}\n反代地址: ${hub_host}\n伪装首页: ${fakePage}`);
    		const isUuid = isUUID(pathname.split('/')[1].split('/')[0]);
    		
    		if (屏蔽爬虫UA.some(fxxk => userAgent.includes(fxxk)) && 屏蔽爬虫UA.length > 0){
    			//首页改成一个nginx伪装页
    			return new Response(await nginx(), {
    				headers: {
    					'Content-Type': 'text/html; charset=UTF-8',
    				},
    			});
    		}
    		
    		const conditions = [
    			isUuid,
    			pathname.includes('/_'),
    			pathname.includes('/r'),
    			pathname.includes('/v2/user'),
    			pathname.includes('/v2/orgs'),
    			pathname.includes('/v2/_catalog'),
    			pathname.includes('/v2/categories'),
    			pathname.includes('/v2/feature-flags'),
    			pathname.includes('search'),
    			pathname.includes('source'),
    			pathname === '/',
    			pathname === '/favicon.ico',
    			pathname === '/auth/profile',
    		];
    
    		if (conditions.some(condition => condition) && (fakePage === true || hostTop == 'docker')) {
    			if (env.URL302){
    				return Response.redirect(env.URL302, 302);
    			} else if (env.URL){
    				if (env.URL.toLowerCase() == 'nginx'){
    					//首页改成一个nginx伪装页
    					return new Response(await nginx(), {
    						headers: {
    							'Content-Type': 'text/html; charset=UTF-8',
    						},
    					});
    				} else return fetch(new Request(env.URL, request));
    			}
    			
    			const newUrl = new URL("https://registry.hub.docker.com" + pathname + url.search);
    
    			// 复制原始请求的标头
    			const headers = new Headers(request.headers);
    
    			// 确保 Host 头部被替换为 hub.docker.com
    			headers.set('Host', 'registry.hub.docker.com');
    
    			const newRequest = new Request(newUrl, {
    					method: request.method,
    					headers: headers,
    					body: request.method !== 'GET' && request.method !== 'HEAD' ? await request.blob() : null,
    					redirect: 'follow'
    			});
    
    			return fetch(newRequest);
    		}
    
    		// 修改包含 %2F 和 %3A 的请求
    		if (!/%2F/.test(url.search) && /%3A/.test(url.toString())) {
    			let modifiedUrl = url.toString().replace(/%3A(?=.*?&)/, '%3Alibrary%2F');
    			url = new URL(modifiedUrl);
    			console.log(`handle_url: ${url}`)
    		}
    
    		// 处理token请求
    		if (url.pathname.includes('/token')) {
    			let token_parameter = {
    				headers: {
    					'Host': 'auth.docker.io',
    					'User-Agent': getReqHeader("User-Agent"),
    					'Accept': getReqHeader("Accept"),
    					'Accept-Language': getReqHeader("Accept-Language"),
    					'Accept-Encoding': getReqHeader("Accept-Encoding"),
    					'Connection': 'keep-alive',
    					'Cache-Control': 'max-age=0'
    				}
    			};
    			let token_url = auth_url + url.pathname + url.search
    			return fetch(new Request(token_url, request), token_parameter)
    		}
    
    		// 修改 /v2/ 请求路径
    		if (/^\/v2\/[^/]+\/[^/]+\/[^/]+$/.test(url.pathname) && !/^\/v2\/library/.test(url.pathname)) {
    			url.pathname = url.pathname.replace(/\/v2\//, '/v2/library/');
    			console.log(`modified_url: ${url.pathname}`)
    		}
    
    		// 更改请求的主机名
    		url.hostname = hub_host;
    
    		// 构造请求参数
    		let parameter = {
    			headers: {
    				'Host': hub_host,
    				'User-Agent': getReqHeader("User-Agent"),
    				'Accept': getReqHeader("Accept"),
    				'Accept-Language': getReqHeader("Accept-Language"),
    				'Accept-Encoding': getReqHeader("Accept-Encoding"),
    				'Connection': 'keep-alive',
    				'Cache-Control': 'max-age=0'
    			},
    			cacheTtl: 3600 // 缓存时间
    		};
    
    		// 添加Authorization头
    		if (request.headers.has("Authorization")) {
    			parameter.headers.Authorization = getReqHeader("Authorization");
    		}
    
    		// 发起请求并处理响应
    		let original_response = await fetch(new Request(url, request), parameter)
    		let original_response_clone = original_response.clone();
    		let original_text = original_response_clone.body;
    		let response_headers = original_response.headers;
    		let new_response_headers = new Headers(response_headers);
    		let status = original_response.status;
    
    		// 修改 Www-Authenticate 头
    		if (new_response_headers.get("Www-Authenticate")) {
    			let auth = new_response_headers.get("Www-Authenticate");
    			let re = new RegExp(auth_url, 'g');
    			new_response_headers.set("Www-Authenticate", response_headers.get("Www-Authenticate").replace(re, workers_url));
    		}
    
    		// 处理重定向
    		if (new_response_headers.get("Location")) {
    			return httpHandler(request, new_response_headers.get("Location"))
    		}
    
    		// 返回修改后的响应
    		let response = new Response(original_text, {
    			status,
    			headers: new_response_headers
    		})
    		return response;
    	}
    };
    
    /**
     * 处理HTTP请求
     * @param {Request} req 请求对象
     * @param {string} pathname 请求路径
     */
    function httpHandler(req, pathname) {
    	const reqHdrRaw = req.headers
    
    	// 处理预检请求
    	if (req.method === 'OPTIONS' &&
    		reqHdrRaw.has('access-control-request-headers')
    	) {
    		return new Response(null, PREFLIGHT_INIT)
    	}
    
    	let rawLen = ''
    
    	const reqHdrNew = new Headers(reqHdrRaw)
    
    	const refer = reqHdrNew.get('referer')
    
    	let urlStr = pathname
    
    	const urlObj = newUrl(urlStr)
    
    	/** @type {RequestInit} */
    	const reqInit = {
    		method: req.method,
    		headers: reqHdrNew,
    		redirect: 'follow',
    		body: req.body
    	}
    	return proxy(urlObj, reqInit, rawLen)
    }
    
    /**
     * 代理请求
     * @param {URL} urlObj URL对象
     * @param {RequestInit} reqInit 请求初始化对象
     * @param {string} rawLen 原始长度
     */
    async function proxy(urlObj, reqInit, rawLen) {
    	const res = await fetch(urlObj.href, reqInit)
    	const resHdrOld = res.headers
    	const resHdrNew = new Headers(resHdrOld)
    
    	// 验证长度
    	if (rawLen) {
    		const newLen = resHdrOld.get('content-length') || ''
    		const badLen = (rawLen !== newLen)
    
    		if (badLen) {
    			return makeRes(res.body, 400, {
    				'--error': `bad len: ${newLen}, except: ${rawLen}`,
    				'access-control-expose-headers': '--error',
    			})
    		}
    	}
    	const status = res.status
    	resHdrNew.set('access-control-expose-headers', '*')
    	resHdrNew.set('access-control-allow-origin', '*')
    	resHdrNew.set('Cache-Control', 'max-age=1500')
    
    	// 删除不必要的头
    	resHdrNew.delete('content-security-policy')
    	resHdrNew.delete('content-security-policy-report-only')
    	resHdrNew.delete('clear-site-data')
    
    	return new Response(res.body, {
    		status,
    		headers: resHdrNew
    	})
    }
    
    async function ADD(envadd) {
    	var addtext = envadd.replace(/[	 |"'\r\n]+/g, ',').replace(/,+/g, ',');	// 将空格、双引号、单引号和换行符替换为逗号
    	//console.log(addtext);
    	if (addtext.charAt(0) == ',') addtext = addtext.slice(1);
    	if (addtext.charAt(addtext.length -1) == ',') addtext = addtext.slice(0, addtext.length - 1);
    	const add = addtext.split(',');
    	//console.log(add);
    	return add ;
    }

    再次点击「部署按钮」。

  • 在 Worker 主页点击「设置」-「触发器」,添加一个自定义域名 docker.x2b.net,如果成功了,在浏览器访问这个域名会跳转到 dockerhub 主页。

  • 继续设置,点击「变量」来添加一个环境变量。变量名 URL302 值为 https://blog.x2b.net,点击「部署」完成。这样当直接访问域名 docker.x2b.net 时会跳转到 blog.x2b.net

在 cf 中的设置完成,接着在本地测试拉取镜像:

root@k8s-204:~# docker pull docker.x2b.net/kubesphere/ks-console:v3.4.1

测试没问题,则将代理地址写入到配置文件:

root@k8s-204:~# vi /etc/docker/daemon.json
{
  "exec-opts": [
      "native.cgroupdriver=systemd"
      ],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "registry-mirrors": [
      "http://192.168.2.253:10007",
      "https://docker.nju.edu.cn",
      "https://docker.x2b.net",
      "https://registry.me-east-1.aliyuncs.com"
      ],
  "insecure-registries": [
      "192.168.2.253:10007",
      "192.168.2.253:10008"
      ]
}
root@k8s-204:~# systemctl daemon-reload
root@k8s-204:~# systemctl restart docker