Skip to main content

入门教程

体系架构

docker 使用(客户端-服务端)的体系架构;其中,docker 守护进程(daemon)负责编译、运行以及分发容器(container)等重要工作,扮演着体系架构中服务端的角色;而 docker 客户端则通过 REST 接口与 docker 守护进程通信,将使用者的指令发送给守护进程,他们既可以处于同一系统内,也可以是远程连接;另外 Docker Compose 也属于客户端,它主要用于编排多个容器的场景。

基本使用

$ docker run -d -p 80:80 docker/getting-started

这是最基本的 docker 运行指令,其中的参数说明如下:

  • -d - 在后台运行容器
  • -p 80:80 - 将主机的 80 端口映射至容器的 80 端口
  • docker/getting-started - 用于创建容器的镜像
tip

上面例子中的参数可简写:

$ docker run -dp 80:80 docker/getting-started

示例应用

在余下的教程中,我们将使用一个示例来说明 docker 的使用,该示例是一个 Node.js 项目;即使你不熟悉 Node.js 也没有关系,这里并不涉及开发内容。该项目地址位于 Github,你可以直接下载 zip 文件。

下载解压后,用你喜欢的编辑器打开,可以看到文件格式如下:

$ ls -l
总用量 172
-rw-rw-r-- 1 wey wey 646 1214 11:11 package.json
drwxrwxr-x 4 wey wey 4096 1214 11:11 spec
drwxrwxr-x 5 wey wey 4096 1214 11:11 src
-rw-rw-r-- 1 wey wey 162208 1214 11:11 yarn.lock

编译镜像

首先需要创建一个 Dockerfile 文件,该文件包含了创建容器镜像所需的指令;在本例中,创建 Dockerfile 如下:

$ ls -l
总用量 172
-rw-rw-r-- 1 wey wey 0 1214 11:27 Dockerfile
-rw-rw-r-- 1 wey wey 646 1214 11:11 package.json
drwxrwxr-x 4 wey wey 4096 1214 11:11 spec
drwxrwxr-x 5 wey wey 4096 1214 11:11 src
-rw-rw-r-- 1 wey wey 162208 1214 11:11 yarn.lock
# syntax=docker/dockerfile:1
FROM node:12-alpine
RUN apk add --no-cache python3 g++ make
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]
caution

注意,Dockerfile 文件是没有扩展名 .txt 的。

然后执行 docker build 命令编译镜像:

$ docker build -t getting-started .

Sending build context to Docker daemon 4.641MB
Step 1/6 : FROM node:12-alpine
12-alpine: Pulling from library/node
...
Status: Downloaded newer image for node:12-alpine
---> 2f014773d54a
Step 2/6 : RUN apk add --no-cache python3 g++ make
...
---> e6f3f854c716
Step 3/6 : WORKDIR /app
---> Running in 3e5816135320
Removing intermediate container 3e5816135320
---> f022642496e7
Step 4/6 : COPY . .
---> e12722c244a5
Step 5/6 : RUN yarn install --production
...
---> f30eb81550d0
Step 6/6 : CMD ["node", "src/index.js"]
---> Running in 6ba0df0212f7
Removing intermediate container 6ba0df0212f7
---> 175c344dd6b8
Successfully built 175c344dd6b8
Successfully tagged getting-started:latest
  1. 命令执行后,按照 Dockerfile 文件指令,首先拉取 node:12-alpine 镜像,因为我们要创建的镜像是基于 node:12-alpine 镜像的;
  2. 镜像拉取完成后,使用 apk 安装额外的包,python3g++make
  3. 使用 COPY 服务项目内容至镜像的 app 目录;
  4. 运行 yarn 安装 Node.js 项目依赖;
  5. 使用 CMD 指定该镜像应用的执行入口;
  6. -t 参数指定编译好的镜像名称;
  7. 最后的 . 参数表明 Dockerfile 文件位于当前目录。

命令执行完,可通过 docker images 查看编译好的镜像:

$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
getting-started latest 175c344dd6b8 16 minutes ago 410MB

运行容器

 $ docker run -dp 3000:3000 getting-started

运行完成后可打开浏览器 http://localhost:3000 地址,可以看到,我们的容易已经正常运行:

更新示例应用

至此,你已经成功将一个 Node.js 项目用 docker 容器运行起来了 ;如果这时,你收到一个需求,需要将页面的上提示文字进行修改。

修改源码

将文件 src/static/js/app.js56 行提示文字修改为:

- <p className="text-center">No items yet! Add one above!</p>
+ <p className="text-center">You have no todo 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 affectionate_lalande
(4691fc1a407ceb191cab03cba96ac5d4301d3d41e47df4f99030bc1285421644):
Bind for 0.0.0.0:3000 failed: port is already allocated
danger

这是因为此前运行容器仍在工作,所以 3000 端口已被占用。

删除旧容器

  1. 查看正在运行的容器

    $ docker ps
    CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
    dc6dc484b0dc 175c344dd6b8 "docker-entrypoint.s…" 2 hours ago Up 2 hours 0.0.0.0:3000->3000/tcp, :::3000->3000/tcp pedantic_euclid
  2. 使用 docker stop 停止容器运行

    $ docker stop <the-container-id>
  3. 当容器停止之后,可以使用 docker rm 删除容器

    $ docker rm <the-container-id>
tip

如果希望使用一条命令删除正在运行的容器,可添加 -f 参数:

$ docker rm -f <the-container-id>

分享应用镜像

分享应用镜像需要使用 docker 镜像仓库;通常 Docker Hub 是默认的镜像仓库,也是我们默认获取其他镜像的地方。

Docker ID

要使用 Docker Hub,必须先注册 Docker ID

创建镜像源

在发布镜像之前,首先需要在 Docker Hub 上创建镜像源:

  1. 注册并登录 Docker Hub
  2. 点击 Create Repository 按钮;
  3. 源名称为 getting-startedVisibility 选择 Public
  4. 点击 Create 按钮。

推送镜像

  1. 登录 Docker Hub

    $ docker login -u YOUR-USER-NAME
  2. 使用 Docker ID 对镜像设置标签:

    $ docker tag getting-started YOUR-USER-NAME/getting-started
  3. 推送镜像:

    $ docker push YOUR-USER-NAME/getting-started

永久存储

容器文件系统

即使创建于同一个镜像,不同的容器包含独立的文件系统。

试验

  1. 创建一个包含 /data.txt 文件的 ubuntu 容器,/data.txt 中的内容是 110000 的随机数据;

    $ docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null"
  2. 验证容器中 /data.txt 文件的内容:

    $ docker ps
    CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
    89699b139c9d ubuntu "bash -c 'shuf -i 1-…" 11 seconds ago Up 10 seconds fervent_proskuriakova

    $ docker exec 89699b139c9d cat /data.txt
    6304
  3. 基于 ubuntu 镜像再创建另外一个容器:

    $ docker run -it ubuntu ls /
    bin dev home lib32 libx32 mnt proc run srv tmp var
    boot etc lib lib64 media opt root sbin sys usr
info

可以看到,在第二个容器中,并没有 /data.txt 文件,这说明每个容器的文件系统都是独立的。

容器 volume

由上面的试验可知,容器的文件系统相互独立,但是使用 volumes 可以将容器中的文件映射至宿主机上,这样即使容器重启,依然能看到此前创建的文件。

永久存储数据示例

  1. 创建 volume

    $ docker volume create todo-db
  2. 使用 volume 创建容器:

    $ docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started
tip

这时的容器已经将存储的数据通过 volume 映射至宿主机,即使将容器删除再重新创建,数据也不会丢失!

深入了解 volume

你一定很好奇 volume 实际存储在哪里?通过命令 docker volume inspect 则可以查看:

$ docker volume ls
DRIVER VOLUME NAME
local todo-db

$ docker volume inspect todo-db
[
{
"CreatedAt": "2021-12-14T15:58:47+08:00",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/todo-db/_data",
"Name": "todo-db",
"Options": {},
"Scope": "local"
}
]

bind mounts

在之前的例子中,我们使用了 volume 来实现数据的永久存储;但是 volume 在宿主机的存储位置是自动分配,并不能由我们自己指定;但有时候我们需要自己来指定 volume 在宿主机的存储位置,这样当文件发生改变时,可以将文件内容直接反应至容器;例如在开发的过程中,对源文件的修改能及时通过测试容器查看,而不是每次都重新编辑镜像以及运行容器。

创建开发模式下的容器

Node.js/home/cooper/getting-started/app)项目目录中运行以下命令:

$ docker run -dp 3000:3000 \
-w /app -v "$(pwd):/app" \
node:12-alpine \
sh -c "yarn install && yarn run dev"
info

该命令直接运行容器,并没有编译镜像,这样很适合处于开发模式的场景。

  • -dp 3000:3000 - 在后台运行容器,将宿主机的 3000 端口映射至容器的 3000 端口;
  • -w /app - 设定容器 /app 目录为工作目录;
  • -v "$(pwd):/app" - 使用 bind mounts 方式将宿主机当前目录挂载至 容器的 /app 目录;
  • node:12-alpine - 项目依赖的镜像;
  • sh -c "yarn install && yarn run dev" - 在容器中执行命令。

容器运行时,可以通过 docker logs -f <container-id> 来查看日志:

$ docker logs -f 941530b27fbf
...
$ nodemon src/index.js
[nodemon] 2.0.13
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node src/index.js`
Using sqlite database at /etc/todos/todo.db
Listening on port 3000
tip

此时,修改源文件后,刷新页面就可以看到变化!

多容器应用

截止到目前,我们的应用只使用到一个容器,但是很多时候都需要同时使用多个容器,比如应用需要连接到数据库 MySQL,那么 MySQL 需要运行在单独的一个容器里面。

tip

如果两个容器位于同一个网络中,他们之间可以通信。

创建 MySQL 容器

  1. 创建网络

    $ docker network create todo-app

    通过 ls 可以查看已创建的网络:

    $ docker network ls
    NETWORK ID NAME DRIVER SCOPE
    b4b3a40366ae bridge bridge local
    b3e0ff4d6425 host host local
    5c7eb6ab0210 todo-app bridge local
  2. 创建 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

    $ docker ps
    CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
    6b364101890f mysql:5.7 "docker-entrypoint.s…" About a minute ago Up About a minute 3306/tcp, 33060/tcp laughing_golick
tip

这里将 todo-mysql-data 映射到了容器的路径 /var/lib/mysql ,但是我并没有提前通过命令 docker volume create 创建该 volume,这是因为 docker 本身会识别并自动创建 volume

  1. 确认 MySQL 数据库已经创建完成:

    docker exec -it <mysql-container-id> mysql -u root -p

    密码输入 secret 后进入数据库。然后查看数据库信息:

     mysql> SHOW DATABASES;
    +--------------------+
    | Database |
    +--------------------+
    | information_schema |
    | mysql |
    | performance_schema |
    | sys |
    | todos |
    +--------------------+
    5 rows in set (0.00 sec)

    可以看到,todos 数据库已成功创建!

连接应用至 MySQL 数据库

$ 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"
tip

这里的 MYSQL_HOST=mysql 对应于创建 MySQL 容器时的 --network-alias mysql

从日志可以看出已经顺利连接上数据库:

$ docker logs <container-id>
[nodemon] 2.0.13
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node src/index.js`
Waiting for mysql:3306.
Connected!
Connected to mysql db at host mysql
Listening on port 3000

Docker Compose

使用 Docker Compose,可以通过定义一个 YAML 文件来编排多个容器。

创建 compose 文件

在项目的根目录下,创建一个 docker-compose.yml 文件,该文件的开头一般为当前支持的版本:

version: "3.7"

定义应用服务

首先来回忆下此前创建容器的命令:

$ 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"

compose 文件中对应的定义如下:

 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 服务

创建 MySQK 容器的命令为:

$ 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

对应的 compose 定义:

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:

运行 compose

启动

$ 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
  • -d - 表示在后台运行应用
info

默认情况下,Docker Compose 会针对定义好的多个应用创建统一的网络。

日志

通过 docker-compose logs -f 可以查看运行日志:

mysql_1  | 2019-10-03T03:07:16.083639Z 0 [Note] mysqld: ready for connections.
mysql_1 | Version: '5.7.27' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server (GPL)
app_1 | Connected to mysql db at host mysql
app_1 | Listening on port 3000
tip

如果想查看其中某一个服务的日志,使用 docker-compose logs -f app

停止

使用 docker-compose down 可停止服务。