# Docker实践
# 开始使用
# 开启docker
执行 docker run -dp 80:80 docker/getting-started
运行一个容器
# Docker 面板
可视化Docker管理,打开该面板即可看到我们刚启动的容器
# 什么是容器
容器只是计算机上的另一个进程,已与主机上的所有其他进程隔离。这种隔离利用了Linux上已有很长时间的内核名称空间和cgroup。
# 什么是容器镜像
运行容器时,它使用隔离的文件系统。此自定义文件系统由容器映像提供。由于映像包含容器的文件系统, 因此它必须包含运行应用程序所需的所有内容-所有依赖项,配置,脚本,二进制文件等。 该映像还包含容器的其他配置,例如环境变量,要运行的默认命令和其他元数据。
# 我们的应用
# 获取 App源码
点击下载 (opens new window),下载完成后解压并打开
# 构建APP 容器镜像
在上一步获取的源码的app
目录里,新建 Dockerfile
文件,输入下面内容
FROM node:12-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]
2
3
4
5
切换到app
目录下 ,输入下面命令开始构建镜像
docker build -t getting-started .
-t
给镜像打上标签,.
符号,表示去当前目录找Dockerfile
# 开启一个App容器
执行下面命名开启一个容器,用我们刚刚打的镜像getting-started
docker run -dp 3000:3000 getting-started
- -d, 在后台运行 detached
- -p 3000:3000 在主机的端口3000到容器的端口3000之间创建映射。
当控制台成功输出容器id的时候,打开浏览器http://localhost:3000/,就可以看到我们的应用。
# 更新容器
需要我们的应用在没有数据的时候提示 You have no todo items yet! Add one above!
# 更新代码
- 把
src/static/js/app.js
的56行 改为<p className="text-center">No items yet! Add one above!</p>
- 重新构建镜像,
docker build -t getting-started .
- 启动新容器,
docker run -dp 3000:3000 getting-started
,此时发现报错
docker: Error response from daemon: driver failed programming external connectivity on endpoint heuristic_greider (cea4c33d11e0f8f1ca148c145dd24b986fcfdaacf9e03957e58072522ee4bfbb): Bind for 0.0.0.0:3000 failed: port is already allocated.
# 替换我们的旧容器
移除一个容器,需要先把他停止,一旦容器被停止了,他就可以被移除了。有两种移除方式:
- 使用Docker CLI 移除
docker ps
获取容器iddocker stop <the-container-id>
停止容器docker rm <the-container-id>
移除容器docker rm -f <the-container-id>
不需要停止,强制删除
- 使用Docker可视化工具删除
# 新建容器
删除我们的旧容器后,执行 docker run -dp 3000:3000 getting-started
新建容器,刷新 http://localhost:3000/
# 分享容器
要共享容器,需要使用Docker registry
,默认使用的是 Docker Hub (opens new window)
# 创建一个仓库
- 登录 Docker Hub
- 点击Create Repository 按钮
- 输入getting-started-xxx,比如我输入了 getting-started-czp
- 点击Create
# 推送镜像到远程仓库
没有tag
- docker login -u YOUR-USER-NAME. 登录Docker Hub
- docker tag getting-started YOUR-USER-NAME/getting-started-czp. 【docker tag 本地镜像源 远程镜像仓库】。 远程没有的时候会自动创建
- docker push YOUR-USER-NAME/getting-started-czp
需要tag
- docker login -u YOUR-USER-NAME. 登录Docker Hub
- docker tag getting-started YOUR-USER-NAME/getting-started-czp:test.
- docker push YOUR-USER-NAME/getting-started-czp:tset
# 测试我们的镜像
- 点击打开测试地址 (opens new window)
- 使用 Docker Hub 账户登录
- 点击 ADD NEW INSTANCE
- docker run -dp 3000:3000 YOUR-USER-NAME/getting-started-czp
- 点击 3000端口号,就会打开刚启动的Docker服务。
# 数据持久化
# 容器的文件系统
当容器运行的时候,它使用镜像中的各个层作为其文件系统。每个容器还拥有自己的“暂存空间”以创建/更新/删除文件。 即使使用相同的镜像,也不会在其他容器中看到任何更改。
# 简单案例
- 执行
docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null"
shuf -i 1-10000 -n 1 -o /data.txt
输出一个 1-10000的随机数到 data.txt
tail -f /dev/null
这个命令是持续输出,保持容器运行
- 在 Docker 可视化工具中打开刚刚启动的ubuntu。执行
cat /data.txt
, 可以看到data.txt
的数据
或者直接执行 docker exec <container-id> cat /data.txt
,也可以看到数据
我们开启一个新的容器
docker run -it ubuntu ls /
并查看文件系统,此时未发现data.txt
移除第一个ubuntu 容器 ,
docker rm -f 32031d768702
# 容器卷 Container Volumes
- 创建容器卷
docker volume create todo-db
- 移除存在的todo-list容器
docker rm -f <id>
- 开启新容器,并加入
-v
参数,docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started
- 打开
http://localhost:3000/
新增几个todo-item - 使用
docker ps
获取所有容器id,使用docker rm -f <id>
删除容器 - 使用第3条命令再次执行
# 深入探讨
使用docker volume inspect todo-db
查看Docker实际上将数据存储在哪里
[
{
"CreatedAt": "2021-02-02T08:28:44Z",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/todo-db/_data", 磁盘上存储数据的实际位置
"Name": "todo-db",
"Options": {},
"Scope": "local"
}
]
2
3
4
5
6
7
8
9
10
11
12
# 使用绑定安装
使用绑定安装,我们可以控制主机上的确切安装点。 我们可以使用它来保留数据,但通常用于向容器中提供其他数据。 在处理应用程序时,我们可以使用绑定挂载将源代码挂载到容器中,以使其查看代码更改,响应并立即查看更改。
# 快速卷类型比较
绑定安装和命名卷是Docker引擎随附的两种主要卷类型。但是,可以使用其他卷驱动程序来支持其他用例(SFTP,Ceph,NetApp,S3等)
# 开始一个开发环境下的容器
- 把源码挂载在容器里
- 安装所有依赖,包括开发依赖
- 启动nodemon 检测文件改动
开始配置环境
- 执行下列代码
docker run -dp 3000:3000 \
-w /app -v "$(pwd):/app" \
node:12-alpine \
sh -c "yarn install && yarn run dev"
2
3
4
- -dp 3000:3000 在后台模式下运行并创建端口映射
- -w /app 设置命令的“工作目录”或当前目录
- -v "$(pwd):/app" 从容器中的主机将当前目录绑定挂载到/ app目录中
- node:12-alpine 和我们Dockerfile 配置的 一样
- sh -c "yarn install && yarn run dev" 安装开发依赖并启动服务
- 查看docker输出日志
docker logs -f --tail=10 <container-id>
- 修改
src/static/js/app.js
文件的{submitting ? 'Adding...' : 'Add Item'}
为{submitting ? 'Adding...' : 'Add'}
- 刷新浏览器可以看到按钮变了。
- 重新构建镜像
docker build -t getting-started .
# 多容器运行
当我们使用node + mongodb开发项目的时候,需要两个容器来跑应用。理由如下
- 数据隔离
- 单独的容器可让您隔离版本和更新版本
- 有可能本地开发的时候需要本地版本,服务器开发的时候需要服务器版本。
- 运行多个进程将需要一个进程管理器(容器仅启动一个进程),这增加了容器启动/关闭的复杂性
# 容器网络
容器间通过网络进行通信,如果两个容器在同一个网络,那么他们能通信,否则不可以。
# 开启MySQL
- 创建一个网络
docker network create todo-app
- 启动一个MySQL容器并将其附加到网络
docker run -d \
--network todo-app --network-alias mysql \
-v todo-mysql-data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=secret \
-e MYSQL_DATABASE=todos \
mysql:5.7
2
3
4
5
6
我们通过-v 连接了卷,但是我们没有通过
docker volume create todo-mysql-data
创建卷。实际上,当我们运行命令的时候 docker发现我们没有todo-mysql-data
,他会给我们创建一个,并挂载。
- 确认下我们的数据库是否运行
docker exec -it <mysql-container-id> mysql -p
- 输入密码
secret
- 登录后输入
SHOW DATABASES;
# 连接到MySQL
使用 nicolaka/netshoot (opens new window) 容器,这个容器有很多有用的网络工具
- 使用
nicolaka/netshoot
镜像开启一个新的容器,确保它连接在同一个网络docker run -it --network todo-app nicolaka/netshoot
- 输入
dig mysql
,输出
[1] 🐳 → dig mysql
; <<>> DiG 9.14.12 <<>> mysql
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 3399
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;mysql. IN A
;; ANSWER SECTION:
mysql. 600 IN A 172.18.0.2
;; Query time: 2 msec
;; SERVER: 127.0.0.11#53(127.0.0.11)
;; WHEN: Tue Feb 02 10:27:24 UTC 2021
;; MSG SIZE rcvd: 44
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ANSWER SECTION: 下的172.18.0.2就是我们的容器通讯ip。
虽然mysql不是有效的主机名,但是Docker知道怎么通过主机名解析到IP。这意味着我们要连接到mysql容器,只需要知道别名就可以。
# 把应用的数据库连接到Mysql
- MYSQL_HOST - the hostname for the running MySQL server
- MYSQL_USER - the username to use for the connection
- MYSQL_PASSWORD - the password to use for the connection
- MYSQL_DB - the database to use once connected
- 执行下来操作
docker run -dp 3000:3000 \
-w /app -v "$(pwd):/app" \
--network todo-app \
-e MYSQL_HOST=mysql \
-e MYSQL_USER=root \
-e MYSQL_PASSWORD=secret \
-e MYSQL_DB=todos \
node:12-alpine \
sh -c "yarn install && yarn run dev"
2
3
4
5
6
7
8
9
查看应用日志
docker logs <container-id>
,会看到 Connected to mysql db at host mysql打开http://localhost:3000/,新增几个todo-item
查看数据库数据
docker exec -it <mysql-container-id> mysql -p todos
输入密码后,执行 select * from todo_items;
可以看到打印的数据
# 容器组合
- 在app目录下新建
docker-compose.yml
,输入下列命令
version: "3.7"
services:
app:
image: node:12-alpine
command: sh -c "yarn install && yarn run dev"
ports:
- 3000:3000
working_dir: /app
volumes:
- ./:/app
environment:
MYSQL_HOST: mysql
MYSQL_USER: root
MYSQL_PASSWORD: secret
MYSQL_DB: todos
mysql:
image: mysql:5.7
volumes:
- todo-mysql-data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: todos
volumes:
todo-mysql-data:
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
删除我们前面启动的容器
执行
docker-compose up -d
,控制台打印
Creating network "app_default" with the default driver
Creating volume "app_todo-mysql-data" with default driver
Creating app_app_1 ... done
Creating app_mysql_1 ... done
2
3
4
- 执行
docker-compose logs -f app
可以查看日志
# Docker面板显示
Docker面板可以看见运行的app组,这个名字默认是docker-compose.yml所在目录的项目名,展开后可以看看见
两个子容器,名字是以 project-name>_<service-name>_<replica-number>
组成的。
# 拆开组
在app目录下执行 docker-compose down
,所有容器将停止,网络被移除。
但是volumes不会被移除,如果需要移除则加上 --volumes
# 构建镜像的最佳实践
# 安全扫描 (opens new window)
当构建镜像的时候使用docker scan
命令进行漏洞检测。
也可以在Docker Hub的配置中设置自动扫描。
# 镜像分层
可以使用
docker image history <image-name>
查看镜像分层。使用此功能,您还可以快速查看每个分层的大小,从而帮助诊断大镜像。使用上面命令的时候看见数据被折断了,可以使用
docker image history --no-trunc getting-started
打印完整的输出。
# 分层缓存
图层更改后,所有下游图层也必须重新创建