0%

docker学习笔记

一、前言

docker是一个将软件虚拟化的工具,可以在任意设备上,建立虚拟机跑软件,实现快速环境搭建运行

二、安装

自行百度,不造轮子了

三、配置

1. docker的默认网段

  • 修改/etc/docker/daemon.json
  • 默认的网段是172.17.0.0/12,size是16。代表分配的网桥段是172.[17-31].0.0/16
  • 下面配置代表新建立的网桥分配段为172.31.[0-255].0/24
  • 例如: 网桥1 172.22.31.1.0/24,网桥2 172.22.31.2.0/24
1
2
3
4
5
6
7
8
{
"default-address-pools": [
{
"base": "172.31.0.0/16",
"size": 24
}
]
}

注意

  • 已经建立好的网桥不会清除,需要停止容器,删除对应网桥重新起容器绑定新的才可以

2. docker的网络模式

  • host: 不生成虚拟网卡和ip,使用宿主机的网络,无法使用端口映射
  • container: 和另一个容器共享ip和端口,compose配置network_mode: "container:[container name/id]"
  • none: 关闭容器网络功能,容器无法联网
  • bridge: 创建一个网桥,虚拟出网卡和ip,通过docker0和iptables配置和主机通信
  • service: compose的概念,和service共享网络,配置network_mode: "service:[service name]"

四、常用命令

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
########## container##########
# 查看所有容器
docker ps -a
# 停止容器
docker stop [container_id]
# 删除container
docker rm [container_id]
# 将某个容器变成自启动
docker update [container_id] --restart=always
# 拷贝文件
docker cp [OPTIONS] [container_id]:[SRC_PATH] [DEST_PATH]
docker cp [OPTIONS] [SRC_PATH] [container_id]:[DEST_PATH]

########## image ##########
# 列出所有image
docker image list
# 导入一个image
docker load -i [image_file]
# 导出一个image,使用image_id打包后导入名字会变成none
docker save [image_id|image_name:tag] -o xxx.tar
# 删除image
docker rmi [image_id]
# 重命名image
docker tag [image_id] xxx

########## logs ##########
# 查看docker日志
# -f : 跟踪日志输出
# --since : 显示某个开始时间的所有日志
# -t : 显示时间戳
# --tail :仅列出最新N条容器日志
docker logs [OPTIONS] [container_name|container_id]

1. docker top 查看docker和宿主机的进程对照关系

1
2
3
4
5
# PID是宿主机进程id,PPID是docker里面的id
=> docker top [container_name|container_id]
UID PID PPID C STIME TTY TIME CMD
nobody 26505 26486 0 2月16 ? 00:02:53 /usr/bin/python3.6 /usr/bin/supervisord -nc /etc/supervisord.conf
nobody 26560 26505 0 2月16 ? 00:00:00 npm

2. docker network 查看docker网络情况

1
2
3
4
5
6
7
=> docker network list
NETWORK ID NAME DRIVER SCOPE
f5efcaf6e2e2 bridge bridge local
237ebc86fb1b host host local
d082a187db55 none null local
# 删除一个网络,最好先停止容器,再删除网络
=> docker network rm [network_id]

3. docker search 搜索镜像

1
2
3
4
5
6
7
=> docker search kali
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
kalilinux/kali-rolling Official Kali Linux Docker image (weekly sna… 786
kasmweb/kali-rolling-desktop Kali Rolling desktop for Kasm Workspaces 11
kalilinux/kali-bleeding-edge Same as kali-rolling with kali-bleeding-edge… 52
kalilinux/kali-last-release Image built from the last snapshot of the of… 55
...

4. docker run 启动镜像

1
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

4.1. 选项

1) 常用选项

  • -d: 后台运行
  • -p [host-port:docker-port]: 端口映射
  • --rm: 停止后删除容器
  • --name [name]: 给容器起名字
  • --network [network-mode]: 网络模式,也可以指定自己建立的网络
  • -i: 启动后无需命令也不停止,就是compose的tty为true
  • -v [host-dir:docker-dir]: 目录映射
  • --restart=always: 开机自启动
  • -e [xxx=val]: 设置环境变量

示例

1
2
3
4
5
# 启动一个image
# -d 后台运行
# -p host:docker 端口映射
# --rm 容器停止后删除容器
docker run -d -p 8085:8080 [image_name]:[tag]

2) cpu限制

  • --cpus=<value>: 限制cpu核心数,--cpus='1.5'代表使用一个半核
  • --cpuset-cpus: 限制使用特定的cpu,0-3代表前4个核,1,3代表第二个和第4个
  • --cpu-period: 设置每个容器进程的调度周期(CFS)下,范围1000-1000000对应单位us
  • --cpu-quota: 设置每个周期内容器能使用的cpu时间,和--cpu-period配合使用,quota大于period代表使用多个核,不能小于1000

示例

1
2
3
4
5
6
7
8
9
# 最大使用1.5个核
docker run -d golang:latest --cpus=1.5

# 限制使用0、2、3这三个核
docker run -d golang:latest --cpuset-cpus=0,2-3

# docker进程调度周期为50ms,每个周期docker使用25ms的cpu,也就是最多占用0.5个核
# quota大于period代表使用多个核
docker run -d golang:latest --cpu-period=50000 --cpu-quota=25000

3) 内存限制

  • -m: 指定内存使用限额,如200M
  • --memory-swap: 设置内存+swap的限额,默认为-m的两倍,也就是内存和swap相同

4) 启动后不退出

(1) 使用tail命令不退出
1
docker run xxx tail -f /dev/null
(2) 使用sleep命令不退出
1
docker run xxx sleep infinity

五、docker-compose.yml解释和编写

1. 基础用法示例

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
# server.yml
version: '3' # 版本号,我也不知道怎么写,随意写个3
services: # 服务,里面写docker容器的服务
sms_server: # 服务名
container_name: sms_server # 指定容器名字,不让容器自己起名字
image: 'node:latest' # 依赖的镜像,这里是不写Dockerfile的关键
ports: # 端口映射关系,host:container
- "7878:7878"
volumes: # 挂载路径,本地是当前目录(yml所在目录),挂载到container的/code目录,并且只读
- .:/code:ro
network_mode: "bridge" # 网络模式,bridge是docker默认的网络模式,可以省略,可选bridge、host、none
environment: # 环境变量
MYSQL_ROOT_PASSWORD: "123456"
GO111MODULE: 'on'
GOPROXY: 'https://goproxy.cn'
working_dir: /code # 工作目录,docker启动后自动cd到此目录
command: ["node", "server.js"] # 容器服务启动的命令
# tty: true # 不设置command可以用这一行不让容器退出
restart: always # docker服务自启动,并且自动重启
privileged: false # 为true代表docker里面的root就是外面的root,否则只是普通用户
cap_add: # 添加权限
- ALL
cap_drop: # 删除权限
- NET_ADMIN
- SYS_ADMIN

2. service和container

  • service是docker-compose的概念,container是docker的概念
  • 一个service可以存在多个container,对应到docker ps就是多个container,docker ps无法查看service的名字
  • 同一个service下面可以起多个相同的容器docker-compose up --scale sms_server=5
    • 不能指定container_name,指定就无法起,默认使用<当前路径名>_<service name>_<序号>起名
    • 如果指定了端口映射,这里也会报错,因为端口被占用

3. cpu限制

  • services.<server_name>.deploy.resources.limits.cpus和docker run的--cpus对应,具体含义见上面解释
1
2
3
4
5
6
7
8
9
10
11
12
13
version: '3'
services:
other-compile:
container_name: other-compile
image: 'cicd_2601/package-env:go1.20.2'
deploy:
resources:
limits:
cpus: 4
network_mode: 'host'
tty: true
privileged: false
restart: always

六、实战

1. 启用一个sms_server服务

根据一个实例可以快速上手

1.1. 需求

  • 将一个nodejs实现的服务器部署到docker上
  • 代码在服务器硬盘,可以实现修改了代码,重启一个服务就可以生效

1.2. 服务器代码

很简单的一个监听7878端口http请求,统一返回一个json的设置cookie的代码,运行也只需要node server.js

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
// server.js

const http = require("http");
const url = require("url");

let json = {
"code": "0",
"llll": "test",
"aaaa": "stest",
"token": "a454as1bbvd5g5s15155"
}

let index = 0;

http.createServer(function (req, res) {
res.setHeader("Set-Cookie", ["c=1;httponly", "b=2", "a=3"]);
res.writeHead(200, { "Content-Type": "application/json", "Content-Length": Buffer.from(JSON.stringify(json)).length })
res.write(JSON.stringify(json))
index++
console.log(`<h3>${index}</h3>`)
req.on("data", function (chunk) {
console.log(chunk.toString())
})
res.end()
}).listen(7878, "0.0.0.0")
console.log("the server is starting");

1.3. 编写docker-compose.yml

  • 使用node镜像作为基础
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# server.yml
version: '3' # 版本号,我也不知道怎么写,随意写个3
services: # 服务,里面写docker容器的服务
sms_server: # 服务名
container_name: sms_server # 指定容器名字,不让容器自己起名字
image: 'node:latest' # 依赖的镜像,这里是不写Dockerfile的关键
ports: # 端口映射关系,host:container
- "7878:7878"
volumes: # 挂载路径,本地是当前目录(yml所在目录),挂载到container的/code目录,并且只读
- .:/code:ro
network_mode: "bridge" # 网络模式,bridge是docker默认的网络模式,可以省略,可选bridge、host、none
# environment: # 环境变量
# MYSQL_ROOT_PASSWORD: "123456"
working_dir: /code # 工作目录,docker启动后自动cd到此目录
command: ["node", "server.js"] # 容器服务启动的命令
# tty: true # 不设置command可以用这一行不让容器退出
restart: always # docker服务自启动,并且自动重启

1.4. 运行

1
2
3
# 指定yml文件,-d后台运行
# --force-recreate 强制重建
sudo docker-compose -f server.yml up -d

2. 使用mysql

2.1. 安装

1
docker pull mysql:latest

2.2. 运行

  • 编写docker-compose.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# server.yml
version: '3' # 版本号,我也不知道怎么写,随意写个3
services: # 服务,里面写docker容器的服务
mysql: # 服务名
container_name: mysql # 指定容器名字,不让容器自己起名字
image: 'mysql:latest' # 依赖的镜像,这里是不写Dockerfile的关键
volumes: # 挂载路径,本地是当前目录(yml所在目录)
- ./data/var_lib_mysql:/var/lib/mysql:rw # 将mysql的数据目录和本地做映射,这样如果mysql容器重启,数据保留
ports: # 端口映射关系,host:container
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: "123456" # root密码
tty: true # 不设置command可以用这一行不让容器退出
restart: always # docker服务自启动,并且自动重启

3. 构建deepin开发环境

基于镜像168447636/deepin20

3.1. 初始化设置

  • 进入docker后初始化
1
2
3
4
5
6
7
# 安装必备软件
apt install zsh tmux git gdb iptables net-tools dnsutils apt-file locales wget curl iputils-ping
apt autoremove
# 更新apt-file
apt-file update
# 设置时区
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
  • 提交改动
1
docker commit deepin_worker deepin_worker

3.2. docker-compose.yml编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
version: '3'
services:
deepin_worker:
container_name: deepin_worker
image: 'deepin_worker:latest'
network_mode: 'bridge'
volumes: # 挂载路径,本地是当前目录(yml所在目录),挂载到container的/code目录,并且只读
- .:/code:rw
environment: # 环境变量
GO111MODULE: 'on'
GOPROXY: 'http://goproxy.cn'
GOSUMDB: 'off'
working_dir: /code # 工作目录,docker启动后自动cd到此目录
tty: true
privileged: true # 可以使用iptables命令
restart: always

七、源码阅读

1. docker各个组件之间的关系

小技巧和踩坑记

1. docker容器内部无法使用iptables命令

  • 容器本身是跟操作系统绑定,并非完整的系统,所以iptables其实操作的是宿主机的
  • 如果是编译环境做测试用的,可以直接加上--privileged--add-cap NET_ADMIN

2. docker pull报错Error: remote trust data does not exist for xxx

明确是信任的镜像,加上DOCKER_CONTENT_TRUST=false运行即可

1
DOCKER_CONTENT_TRUST=false docker pull xxx

3. docker修改镜像和容器存储目录

docker的默认目录在/var/lib/docker,如果根目录分配比较小的情况下会导致根目录占满

1
2
=> docker info | grep -i root
Docker Root Dir: /var/lib/docker

修改此目录按照下面的命令执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 先停止docker服务
=> systemctl stop docker docker.socket
# 拷贝到新目录
=> cp -r /var/lib/docker /path/to/new
# 修改docker配置文件
=> vim /etc/docker/daemon.json
# 修改如下内容
# {
# "data-root": "/path/to/new"
# }

# 重启docker即可
=> systemctl start docker
# 老的目录删掉
=> rm -rf /var/lib/docker

4. docker run报错docker: Error: error contacting notary server: dial tcp 154.83.15.45:443: i/o timeout.

和上面第2点是一样的,都是不信任导致,临时规避方式一样

1
DOCKER_CONTENT_TRUST=0 docker run xxx