docker教程

安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 1. 卸载旧版本docker
yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine

# 2. 安装docker的yum库
yum install -y yum-utils

# 3. 配置docker的yum源
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

# 4. 安装docker
yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
docker -v # 检查是否安装成功
docker images # 检查是否启动了docker服务
# 安装docker后,默认docker0占用172.17.0.0网段(route -n),如果内网的其他服务器有使用这个网段,需要修改docker0占用的网段到其他网段(如192.168.x.x)

# 5. 启动和校验docker
systemctl enable docker # 开机自启
systemctl start docker # 首次手动开启


# 6. 后续管理
systemctl stop docker
systemctl restart docker

# 7. 用户加入docker组
sudo usermod -aG docker your_username

换源

随便找个国内源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
```bash
# 创建目录
mkdir -p /etc/docker

# 进入上面那个目录后再依次执行下面的命令
# 复制内容,注意把其中的镜像加速地址改成你自己的
tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://xxxx.mirror.aliyuncs.com"]
}
EOF

# 重新加载配置
systemctl daemon-reload

# 重启Docker
systemctl restart docker

自建:CF反代官方源

  1. CF新建worker,用下面连接的代码替换示例代码即可
    1. https://github.com/jonssonyan/cf-workers-proxy/blob/main/docker.js
  2. 绑定自定义域名(子域名就行)
  3. 如果是cloudns的域名,去cloudns上新增A地址指向CF的ip(如:172.67.42.76),等上面转圈结束即可
  4. 使docker生效
    1. 修改/etc/docker/daemon.json文件,添加 "registry-mirrors": ["https://xxxx.mirror.aliyuncs.com"]
    2. 重载配置并重启docker:systemctl daemon-reload && systemctl restart docker
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
function logError(request, message) {
console.error(
`${message}, clientIp: ${request.headers.get(
"cf-connecting-ip"
)}, user-agent: ${request.headers.get("user-agent")}, url: ${request.url}`
);
}

function createNewRequest(request, url, proxyHostname, originHostname) {
const newRequestHeaders = new Headers(request.headers);
for (const [key, value] of newRequestHeaders) {
if (value.includes(originHostname)) {
newRequestHeaders.set(
key,
value.replace(
new RegExp(`(?<!\\.)\\b${originHostname}\\b`, "g"),
proxyHostname
)
);
}
}
return new Request(url.toString(), {
method: request.method,
headers: newRequestHeaders,
body: request.body,
});
}

function setResponseHeaders(
originalResponse,
proxyHostname,
originHostname,
DEBUG
) {
const newResponseHeaders = new Headers(originalResponse.headers);
for (const [key, value] of newResponseHeaders) {
if (value.includes(proxyHostname)) {
newResponseHeaders.set(
key,
value.replace(
new RegExp(`(?<!\\.)\\b${proxyHostname}\\b`, "g"),
originHostname
)
);
}
}
if (DEBUG) {
newResponseHeaders.delete("content-security-policy");
}
let docker_auth_url = newResponseHeaders.get("www-authenticate");
if (docker_auth_url && docker_auth_url.includes("auth.docker.io/token")) {
newResponseHeaders.set(
"www-authenticate",
docker_auth_url.replace("auth.docker.io/token", originHostname + "/token")
);
}
return newResponseHeaders;
}

/**
* 替换内容
* @param originalResponse 响应
* @param proxyHostname 代理地址 hostname
* @param pathnameRegex 代理地址路径匹配的正则表达式
* @param originHostname 替换的字符串
* @returns {Promise<*>}
*/
async function replaceResponseText(
originalResponse,
proxyHostname,
pathnameRegex,
originHostname
) {
let text = await originalResponse.text();
if (pathnameRegex) {
pathnameRegex = pathnameRegex.replace(/^\^/, "");
return text.replace(
new RegExp(`((?<!\\.)\\b${proxyHostname}\\b)(${pathnameRegex})`, "g"),
`${originHostname}$2`
);
} else {
return text.replace(
new RegExp(`(?<!\\.)\\b${proxyHostname}\\b`, "g"),
originHostname
);
}
}

async function nginx() {
return `<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
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>`;
}

export default {
async fetch(request, env, ctx) {
try {
let {
PROXY_HOSTNAME = "registry-1.docker.io",
PROXY_PROTOCOL = "https",
PATHNAME_REGEX,
UA_WHITELIST_REGEX,
UA_BLACKLIST_REGEX,
URL302,
IP_WHITELIST_REGEX,
IP_BLACKLIST_REGEX,
REGION_WHITELIST_REGEX,
REGION_BLACKLIST_REGEX,
DEBUG = false,
} = env;
const url = new URL(request.url);
const originHostname = url.hostname;
if (url.pathname.includes("/token")) {
PROXY_HOSTNAME = "auth.docker.io";
} else if (url.pathname.includes("/search")) {
PROXY_HOSTNAME = "index.docker.io";
}
if (
!PROXY_HOSTNAME ||
(PATHNAME_REGEX && !new RegExp(PATHNAME_REGEX).test(url.pathname)) ||
(UA_WHITELIST_REGEX &&
!new RegExp(UA_WHITELIST_REGEX).test(
request.headers.get("user-agent").toLowerCase()
)) ||
(UA_BLACKLIST_REGEX &&
new RegExp(UA_BLACKLIST_REGEX).test(
request.headers.get("user-agent").toLowerCase()
)) ||
(IP_WHITELIST_REGEX &&
!new RegExp(IP_WHITELIST_REGEX).test(
request.headers.get("cf-connecting-ip")
)) ||
(IP_BLACKLIST_REGEX &&
new RegExp(IP_BLACKLIST_REGEX).test(
request.headers.get("cf-connecting-ip")
)) ||
(REGION_WHITELIST_REGEX &&
!new RegExp(REGION_WHITELIST_REGEX).test(
request.headers.get("cf-ipcountry")
)) ||
(REGION_BLACKLIST_REGEX &&
new RegExp(REGION_BLACKLIST_REGEX).test(
request.headers.get("cf-ipcountry")
))
) {
logError(request, "Invalid");
return URL302
? Response.redirect(URL302, 302)
: new Response(await nginx(), {
headers: {
"Content-Type": "text/html; charset=utf-8",
},
});
}
url.host = PROXY_HOSTNAME;
url.protocol = PROXY_PROTOCOL;
const newRequest = createNewRequest(
request,
url,
PROXY_HOSTNAME,
originHostname
);
const originalResponse = await fetch(newRequest);
const newResponseHeaders = setResponseHeaders(
originalResponse,
PROXY_HOSTNAME,
originHostname,
DEBUG
);
const contentType = newResponseHeaders.get("content-type") || "";
let body;
if (contentType.includes("text/")) {
body = await replaceResponseText(
originalResponse,
PROXY_HOSTNAME,
PATHNAME_REGEX,
originHostname
);
} else {
body = originalResponse.body;
}
return new Response(body, {
status: originalResponse.status,
headers: newResponseHeaders,
});
} catch (error) {
logError(request, `Fetch error: ${error.message}`);
return new Response("Internal Server Error", { status: 500 });
}
},
}

简单使用

docker的主要概念是镜像和容器,镜像和容器的关系就好像编程世界的类(class)和实例(instance)

  • 镜像:可以简单理解成操作系统的安装包。通过镜像可以在docker中示例化出多个容器,就好像电脑上可以装多个系统一样
  • 容器:通过镜像实例化出来的,一个镜像可以实例化出多个容器。就好像windows的操作系统安装包镜像可以装到多个电脑上一样。

简单工作流

从dockerhub上拉取基础镜像,实例化出容器后,在容器内安装各种依赖和工具,然后将运行中的容器打包保存成新的镜像,最后通过docker run实例化新镜像成一个服务

① 拉取镜像

1
2
docker pull centos:8.4.2105  # 从dockerhub拉取centos镜像保存到本地(版本为8.4.2105)
docker image ls # 查看本地已有的镜像

② 实例化并安装各种依赖和工具

  1. 将镜像实例化成容器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
docker run -it \
-v /root/.superset:/root/.superset \
-e TZ=Asia/Shanghai \
-p 9999:9999 \
centos:8.4.2105 \
bash
# docker run: 将镜像实例化成容器的命令
# -it: -i和-t的常用连写,通常用于以交互模式运行容器,允许用户与容器内的进程进行交互
# -i是--interactive,作用是保持标准输入(stdin)打开,即使没有连接到终端
# -t是--tty,作用是为容器分配一个伪终端(pseudo-TTY)
# centos:8.4.2105:镜像名
# bash:启动容器后执行的命令,-it常和bash一起使用。也可以是其他任意命令
# -v:将本地的路径映射到容器内的路径,常用于持久化容器内的数据(因为容器一旦退出或重启,里面产生的数据都不会保存,因为要重新从镜像实例化出一个容器,就跟电脑重装系统一样)
# -p:如果容器内部署了对外服务,则需要分配端口,因此将本地的9999端口映射到容器内的9999端口。一旦映射绑定后,所有内网和公网来访问本机9999的请求,都被转发到容器内的9999端口
  1. 进入容器后就跟普通的centos终端一样,安装各种依赖和工具。如superset
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 容器内执行

# 配置 yum 国内源
cd /etc/yum.repos.d/
mkdir backup && mv *repo backup/
curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-8.repo
sed -i -e"s|mirrors.cloud.aliyuncs.com|mirrors.aliyun.com|g " /etc/yum.repos.d/CentOS-*
sed -i -e "s|releasever|releasever-stream|g" /etc/yum.repos.d/CentOS-*
yum clean all && yum makecache

# 安装 miniconda
mkdir -p ~/.conda
curl -o ~/.conda/miniconda.sh https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
bash ~/.conda/miniconda.sh -b -u -p ~/.conda && rm -f ~/.conda/miniconda.sh

# 激活并初始化 miniconda
source ~/.conda/bin/activate && conda init --all && source ~/.bashrc
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple # 全局设置清华源
pip config set global.index-url https://mirrors.aliyun.com/pypi/simple # 全局设置阿里源

# conda安装 superset 虚拟环境
dnf install -y gcc gcc-c++ libffi-devel python3-devel python3-pip python3-wheel openssl-devel cyrus-sasl-devel openldap-devel
#yum install cyrus-sasl-plain cyrus-sasl-devel cyrus-sasl-gssapi # 非必须:添加hive连接时报错sasl相关错误时需要
conda create -y -n py39superset python=3.9
conda activate py39superset
pip install --upgrade pip -i https://mirrors.aliyun.com/pypi/simple
pip install apache-superset -i https://mirrors.aliyun.com/pypi/simple

# 初始化 superset 应用
export SUPERSET_SECRET_KEY="oh-my-recommend" && export FLASK_APP="superset"
superset db upgrade
superset fab create-admin --username admin --firstname admin --lastname admin --email zhoutao@newlang.cn --password ohmygod
superset init

# 改成中文界面
sed -i 's/BABEL_DEFAULT_LOCALE = "en"/BABEL_DEFAULT_LOCALE = "zh"/' $CONDA_PREFIX/lib/python3.9/site-packages/superset/config.py
sed -i 's/LANGUAGES = {}/LANGUAGES = {"zh": {"flag": "cn", "name": "简体中文"}, "en": {"flag": "us", "name": "English"}}/' $CONDA_PREFIX/lib/python3.9/site-packages/superset/config.py

# 安装依赖包支持 hive连接
pip install pyhive sasl thrift thrift-sasl pyhs2 -i https://mirrors.aliyun.com/pypi/simple/

# 启动 superset web应用
SUPERSET_SECRET_KEY="oh-my-recommend" FLASK_APP=superset superset run -h "0.0.0.0" -p 9999 --with-threads --reload --debugger
  1. 安装并测试完后,记得删除容器内的各种临时数据和缓存,减小后续打包的新镜像体积
1
2
3
# 容器内执行:清理临时数据和缓存
yum clean all && dnf clean all && conda clean -y --all && pip cache purge
rm -rf /tmp/* /var/tmp/* /var/log/*/* ~/.cache/*

③ 将运行中的容器打包保存成新的镜像

新开一个本地终端,按需执行如下命令:

1
2
3
4
5
6
7
8
# 将运行中的容器打包保存成新的镜像
docker container ls # 查看运行中的容器,找到目标容器的container_id
docker commit <container_id> <新镜像名>:<版本号> # 将运行中的容器打包保存成新的镜像
docker image ls # 查看新镜像信息

# 如果打包不满意(如忘记清理容器内缓存导致镜像体积太大了),可以删掉重来
docker rmi <新镜像名>:<版本号> # 删除新镜像
docker image prune -a # 清理已经没有容器依赖的层数据

对新镜像满意后,可exit退出释放容器

④ 实例化新镜像成一个服务

如superset web服务:

1
2
3
4
5
6
7
8
docker run -d \
--name superset4 \
--restart unless-stopped \
--cpus='4.0' --memory='4g' \
--network=host \
-v /root/.superset:/root/.superset \
running_superset4:20250120 \
bash -c 'source ~/.bashrc && conda activate py39superset && SUPERSET_SECRET_KEY="oh-my-recommend" FLASK_APP=superset superset run -h "0.0.0.0" -p 9999 --with-threads --reload --debugger'

镜像制作和推送

通过 Dockerfile 制作镜像,并将新镜像推送到 dockerhub 云端

  1. 拉取基础镜像,并将yum的源修改为国内阿里源,打包成新镜像。Dockerfile如下
1
2
3
4
5
6
7
8
9
FROM centos:8.4.2105

RUN cd /etc/yum.repos.d/ && \
mkdir backup && mv *repo backup/ && \
curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-8.repo && \
sed -i -e"s|mirrors.cloud.aliyuncs.com|mirrors.aliyun.com|g " /etc/yum.repos.d/CentOS-* && \
sed -i -e "s|releasever|releasever-stream|g" /etc/yum.repos.d/CentOS-* && \
yum clean all && dnf clean all && rm -rf /var/cache/yum/* && \
rm -rf /tmp/* /var/tmp/* /var/log/*/* ~/.cache/*
  1. 执行命令构建新镜像 docker build -t centos:latest ./
  2. 将新镜像推送到 dockerhub
1
2
3
4
5
6
myProxyHost=""  # 反代地址(若有填上,如docker.zzftt.xxx.com/)
myDockerHubUserName="tonyzzftt"
docker login -u ${myDockerHubUserName} --password-stdin ${myProxyHost}
docker tag centos:latest ${myProxyHost}${myDockerHubUserName}/centos:latest
docker push ${myProxyHost}${myDockerHubUserName}/centos:latest
docker rmi ${myProxyHost}${myDockerHubUserName}/centos:latest

参考资料