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