Docker

container(容器)

Simply put, a container is simply another process on your machine that has been isolated from all other processes on the host machine.

容器就是一个独立于其他所有主机上的进程的进程

container image (镜像)

This custom filesystem is provided by a container image. Since the image contains the container’s filesystem, it must contain everything needed to run an application - all dependencies, configuration, scripts, binaries, etc. The image also contains other configuration for the container, such as environment variables, a default command to run, and other metadata

镜像是容器的文件系统,提供运行程序的所有文件和容器的配置文件

使用Container和Image 的 Steps

  1. 将源代码置入Docker(clone)

  2. 构建app的container image

    • 创建一个Dockerfile 文件
    1
    2
    3
    4
    5
    FROM node:10-alpine
    WORKDIR /app
    COPY . .
    RUN yarn install --production
    CMD ["node", "/app/src/index.js"]
    • 使用docker build构建container image
    1
    docker build -t docker-101 .

    说明:docker-101是这个container image的名字 . 代表本地目录(其中包含Dockerfile)

    FROM 从node:10-alpine镜像开始构建

    复制到本地

    用yarn安装应用依赖

    CMD指示从image开启container的默认命令

  3. 开启app容器

    • 1
      docker run -dp 3000:3000 docker-101

-d 表示 detached模式(后台运行)-p创建主机端口3000到容器端口3000的映射

主机端口只能有一个进程占用。

想要映射之前的端口,需要替换掉旧的容器

  • docker ps 查询容器信息
  • docker stop 杀死容器
  • docker rm 移除容器
  • docker rm -f 杀死并移除容器

分享image

分享image需要使用docker registry注册器,默认的registry是Docker HUB

创建一个repo(仓库)

  1. 前往 Docker Hub
  2. 创建仓库
  3. 设置名字和visibility
  4. 创建

Publish Image

docker image ls 显示image信息

  • 登录到dockerhubdocker login -u YOUR-USER-NAME.
  • tag imagedocker tag docker-101 YOUR-USER-NAME/101-todo-app
  • 发布到dockerHUbdocker push YOUR-USER-NAME/101-todo-app

Persisting our DB

容器的文件系统

当容器运行时,使用多个layer从image到文件系统,每个容器也有自己的scratch space来创建/更新/删除 文件。所以容器上的修改对于其他容器是不可见的,即使使用同样的image

1
docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null"

Validate we can see the output by exec‘ing into the container. To do so, you need to get the container’s ID (use docker ps to get it).

1
docker exec <container-id> cat /data.txt

Container Volumes

Volumes provide the ability to connect specific filesystem paths of the container back to the host machine. If a directory in the container is mounted, changes in that directory are also seen on the host machine. If we mount that same directory across container restarts, we’d see the same files.

卷提供了将容器的特定文件系统路径连接回主机的功能。 如果装入了容器中的目录,则在主机上也会看到该目录中的更改。 如果在重新启动容器时安装相同的目录,则会看到相同的文件。

  1. 使用docker volume create来创建一个卷
1
docker volume create todo-db
  1. 开启容器,加上-v表明挂载一个卷
1
docker run -dp 3000:3000 -v todo-db:/etc/todos docker-101

docker volume inspect

1
2
3
4
5
6
7
8
9
10
11
12
docker volume inspect todo-db
[
{
"CreatedAt": "2019-09-26T02:18:36Z",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/todo-db/_data",
"Name": "todo-db",
"Options": {},
"Scope": "local"
}
]

Using Bind Mounts

With bind mounts, we control the exact mountpoint on the host. We can use this to persist data, but is often used to provide additional data into containers. When working on an application, we can use a bind mount to mount our source code into the container to let it see code changes, respond, and let us see the changes right away.

例子:开始一个开发模式容器

  1. 把源代码挂载到容器
  2. 安装所有依赖,包含开发依赖
  3. 开启nodemon(类似工具)来检测文件变化
  4. 修改代码
  5. 刷新网页来查看变化
  6. 一旦结束修改,stop容器并就构建自己的新的镜像docker build -t getting-started .
1
2
3
4
docker run -dp 3000:3000 \
-w /app -v $PWD:/app \
node:10-alpine \
sh -c "yarn install && yarn run dev"
指令 说明
-dp 3000:3000 用后台模式运行,并且创建一个端口映射
-w /app 设置工作目录,指令从此处开始
-v ${PWD}:/app 绑定挂载容器中的主机当前目录到/app中
node:12-alpine 使用的镜像
sh -c “yarn install && yarn run dev” 指令。运行一个shell指令,运行yarn install来安装所有依赖,运行yarn run dev开始开发环境

docker logs -f 查看log

Multi-Container Apps 多容器应用

每个容器只做一件事

Container Networking

默认情况,容器独立运行。如何实现容器间的通信,需要使用网络。当两个容器在同一网络中,他们能互相通信。

开启MySQL

有两种方式能把容器放在网路中:1)在开始的时候声明。2)连接到一个已经存在的容器

  1. 创建网络

docker network create todo-app

  1. 开启一个MySQL容器并把它连接到网络
1
2
3
4
5
6
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

这里创建了一个volume名叫todo-mysql-data并挂载到/var/lib/mysql上,不需要使用docker volume create, Docker会检测并自动为我们创建

  1. 连接到mysql
1
docker exec -it <mysql-container-id> mysql -p
  1. 使用mysql
1
mysql> SHOW DATABASES;

连接到MySQL

如果我们在同一个网络运行另一个容器,如何找到这个容器?

使用nicolaka/netshoot容器,这是一个搭载很多用于troubleshooting和网络问题debugging的工具镜像

  1. 开启一个使用这个工具镜像的容器
1
docker run -it --network todo-app nicolaka/netshoot
  1. 在这个容器中,使用dig命令,DNS工具用于查询ip地址
1
dig mysql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
; <<>> DiG 9.14.1 <<>> mysql
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32162
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;mysql. IN A

;; ANSWER SECTION:
mysql. 600 IN A 172.23.0.2

;; Query time: 0 msec
;; SERVER: 127.0.0.11#53(127.0.0.11)
;; WHEN: Tue Oct 01 23:47:24 UTC 2019
;; MSG SIZE rcvd: 44

ANSWER SECTION

A 记录了mysql ip地址 172.18.0.2

使用MySQL运行app

The todo app supports the setting of a few environment variables to specify MySQL connection settings. They are:

  • 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
  1. 定义环境变量,连接容器到app network
1
2
3
4
5
6
7
8
9
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"
  1. 用docker logs container-id查看
1
2
3
4
5
6
7
8
# Previous log messages omitted
$ nodemon src/index.js
[nodemon] 1.19.2
[nodemon] to restart at any time, enter `rs`
[nodemon] watching dir(s): *.*
[nodemon] starting `node src/index.js`
Connected to mysql db at host mysql
Listening on port 3000
  1. 在网页app中修改
  2. 连接到mysql 查看
1
docker exec -ti <mysql-container-id> mysql -p todos
1
2
3
4
5
6
7
mysql> select * from todo_items;
+--------------------------------------+--------------------+-----------+
| id | name | completed |
+--------------------------------------+--------------------+-----------+
| c906ff08-60e6-44e6-8f49-ed56a0853e85 | Do amazing things! | 0 |
| 2912a79e-8486-4bc3-a4c5-460793a575ab | Be awesome! | 0 |
+--------------------------------------+--------------------+-----------+

使用Docker Compose

Docker Compose is a tool that was developed to help define and share multi-container applications. With Compose, we can create a YAML file to define the services and with a single command, can spin everything up or tear it all down.

查看version

1
docker-compose version

创建Compose文件

  1. 在app项目root中,创建一个名为docker-compose.yml的文件
  2. 在compose文件中,先定义version,可通过Compose file reference查看最新版本
  3. 接下来,定义需要的service或容器列表

定义app service

  1. 首先为容器定义service entry和image,可以为其选择任意名字,这个名字会自动成为network alias
  2. 接下来定义command
  3. 通过ports定义接口映射
  4. 使用working_dir定义工作路径,volumes定义volume mapping
  5. 使用environment定义环境变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 service

  1. 定义新服务,并命名为mysql来自动创建network alias,定义image
  2. 接下来,定义volume mapping,用docker run运行时,命名的volume会自动创建,但是用compose不会,需要使用volumes来定义
  3. 定义环境变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: "3.7"

services:
app:
# The app service definition
mysql:
image: mysql:5.7
volumes:
- todo-mysql-data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: todos

volumes:
todo-mysql-data:

docker-compose.yml 例子

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
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:

运行app stack

使用docker-compose up 来开启app stack,-d 后台

使用docker-compose logs -f来查看logs -f follow,可以查看即时输出

使用docker-compose down 来拆除容器

Image Building Practices

Image Layering

使用docker image history来查看layers

Layer Caching

一旦一个层被修改了,它的下游层都要修改

回到映像历史记录输出,我们看到Dockerfile中的每个命令都成为映像中的新层。 您可能还记得,当我们对映像进行更改时,必须重新安装yarn依赖项。 有没有办法来解决这个问题? 每次我们构建时都带着相同的依赖关系是没有意义的,对吧?

要解决此问题,我们需要重组Dockerfile来帮助支持依赖项的缓存。 对于基于节点的应用程序,那些依赖项在package.json文件中定义。 因此,如果我们仅先复制该文件,安装依赖项,然后再复制其他所有内容,那该怎么办? 然后,仅当package.json发生更改时,我们才重新创建yarn依赖项。 合理?

更新Dockerfile以首先复制到package.json中,安装依赖项,然后再复制其他所有内容。

1
2
3
4
5
6
FROM node:12-alpine
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --production
COPY . .
CMD ["node", "/app/src/index.js"]

Multi-Stage Builds

好处:

  • Separate build-time dependencies from runtime dependencies
  • Reduce overall image size by shipping only what your app needs to run

Maven/Tomcat 例子

1
2
3
4
5
6
7
FROM maven AS build
WORKDIR /app
COPY . .
RUN mvn package

FROM tomcat
COPY --from=build /app/target/file.war /usr/local/tomcat/webapps

In this example, we use one stage (called build) to perform the actual Java build using Maven. In the second stage (starting at FROM tomcat), we copy in files from the build stage. The final image is only the last stage being created (which can be overridden using the --target flag).

React 例子

1
2
3
4
5
6
7
8
9
10
FROM node:12 AS build
WORKDIR /app
COPY package* yarn.lock ./
RUN yarn install
COPY public ./public
COPY src ./src
RUN yarn run build

FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html