+-
docker 使用入门

docker 使用入门

在公司使用微服务之后,因为各个服务之间有依赖关系,调试自己服务的时候每次都需要编译运行其他服务,原来使用一台开发机作为开发环境的方式就很麻烦,所以考虑用 docker 部署一套可以复用的开发环境。首先了解下docker的基本使用。

安装

点击链接下载 docker-desktop ,然后傻瓜式安装。

安装好之后可以在命令行执行 docker --version 命令,查看 docker 是否正常运行。

教程

官方肯定有教程的,所以可以去官网找一下,我这边看的是 docker/getting-started 的教程。docker/getting-started 本身就是一个 dockerrepo。下面说一下怎么看这个教程。

在安装好 docker 之后,在命令行运行 docker run -d -p 80:80 docker/getting-started 命令, 然后就启动了一个容器。

然后本地浏览器访问 http://localhost/tutorial/ 可以看到教程。英文的,但是很好理解,有点英语基础都可以看懂。本篇教程也是按照这个教程来的,记录下方便以后自己用。

接下来只讲使用,不讲原理。

使用

创建第一个自己的APP

教程提供了一个 Node.js 的应用让我们直接使用,点击下载 Node.js APP , 本地的连接,上述步骤没做下载不下来。

将下载的 zip 文件解压,用编辑器打开, 这边我用的是 vscode

创建该APP镜像

在下载的 APP 的跟目录下创建一个名称为 Dockerfile 的文件,文件内容如下:
FROM node:12-alpine
RUN apk add --no-cache python g++ make
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]
APP 跟目录下运行如下命令:
docker build -t getting-started .

-t 是给镜像取个名字,名字是 getting-started 。 最后的 . 代表的是运行的是当前目录下的 Dockerfile 文件。

启动一个APP容器

使用 docker run 命令启动一个容器,启动的时候要指定使用刚才构建的镜像。命令如下:
docker run -dp 3000:3000 getting-started
启动之后,在本地浏览器输入 http://localhost:3000 。可以看到一个 todo list 应用。 你可以在应用中使用一下。如果是自己的应用基本就可以使用了。

你现在可以看下下载的 docker-desktop 面板,里面有两个容器,一个是用来看教程的,一个是新创建的自己的 APP

更新APP容器

启动容器后,容器内的应用代码肯定不是一成不变的,加个需求不是很正常的事情嘛,所以需要在更改代码之后,重新构建镜像,用新镜像重新启动容器。

修改代码,在 src/static/js/app.js 文件下更新第 56 行
-                <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 .
删除原有容器,不删除原有容器直接启动会因为 3000 端口占用启不起来。可以用命令删除,或者直接在 docker-desktop 面板上直接删除。下面介绍下命令删除。 首先用命令 docker ps 找到所有启动的容器。如下图:
图中第一列就是容器id,然后还用 docker rm <the-container-id> 命令删除该容器。 根据新镜像重新启动容器,命令如下。
docker run -dp 3000:3000 getting-started
重新看下应用 http://localhost:3000 。

持久化数据库

docker 中每个容器的文件系统都是独立的,一个容器无法访问到另一个容器的文件。同一个容器,再重启后旧容器中的文件就丢了。所以上面的 todo list APP 再容器更新后之前加的 todo 就没了。那怎么让容器更新后还能访问到之前的数据呢?

这里引出容器的 volumes 概念。 volumes 会将容器内的目录映射到宿主机的目录上,然后每次启动的时候都加载宿主机的目录,文件就不会丢了。

有两种类型的 volumes,一种是 named volumes , 另一种是 bind mounts

named volumes

首先讲一下 named volumes :

使用 docker volume create 命令创建一个 volume 。
docker volume create todo-db
删除已经存在的 todo list APP 容器。步骤上面已经说了。 用 docker run 命令重新启动容器,这次启动要加 -v 参数,命令如下:
docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started

-v 就是指定 volumes 的意思。

刷新应用 http://localhost:3000 , 加几条新的 todo 。 然后删除 APP 容器,重新启动一个。 再次刷新应用,确认刚才的几条 todo 没有丢。

注:查看 Valumes 信息命令:

docker volume inspect <valumes name>

bind mounts

然后再来说一下 bind mounts ,它会将宿主机内的目录与容器内的目录绑定,然后改变宿主机目录下的内容,容器内的也会改变,这就可以实现在宿主机改代码,及时同步到容器内。使用流程:

关闭所有的 getting-started 容器。 运行以下命令:
docker run -dp 3000:3000 \
    -w /app -v "$(pwd):/app" \
    node:12-alpine \
    sh -c "yarn install && yarn run dev"

如果是 PowerShell ,运行如下命令:

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

命令解释如下:

-dp :是参数 -d , -p 的缩写, -d代表后台运行, -p 指定端口。 -w /app :容器内工作目录,程序运行的目录。 -v "$(pwd):/app" : 这里指定了 volumes , 将宿主机的当前目录与容器的 /app 目录绑定。 node:12-alpine :使用的镜像 sh -c "yarn install && yarn run dev" :运行 shell 用命令 docker logs -f <container-id> 查看日志,如果你看到以下内容代表启动成功:
docker logs -f <container-id>
$ 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`
Using sqlite database at /etc/todos/todo.db
Listening on port 3000
尝试着改代码,将 src/static/js/app.js 文件的 109 行 "Add Item" 改成 "Add":
-                         {submitting ? 'Adding...' : 'Add Item'}
+                         {submitting ? 'Adding...' : 'Add'}
刷新网页看效果。

创建一个多容器的 APP

todo list APP 之前用的是 SQLite Database 来作为存储的。现在使用个更通用的,比如 MYSQL。 当然可以直接把 MYSQLAPP 部署到同一台机器上,但是 docker 不建议这么做。 docker 希望每个容器内的功能单一。所以现在的部署方案是两个容器,一个部署 MYSQL ,一个部署 APP 。 像这样:

这样两个容器之间需要通过网络连接。这样就引出 network 概念。同一个 network 上的容器可以相互通信。

创建启动MYSQL容器

创建一个 network :
docker network create todo-app
启动一个 MYSQL 容器,将该容器连接到上一步创建的 network 上面。
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

如果用的 PowerShell

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

这里用到了 --network-alias, 该属性的意思是: 其他容器连接 mysql 填写 host 的时候,直接填 --network-alias 的值 mysql 就行。相当于域名。

确认 mysql容器是否正常运行
docker exec -it <mysql-container-id> mysql -p

连接到mysql之后运行:

mysql> SHOW DATABASES;

可以看到 todos 库:

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

启动 APP 容器连接到 MYSQL

关闭所有的 getting-started 容器。 重新启动容器
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"

如果是PowerShell :

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"

新增了 --network todo-app 来连接到 todo-app 网络,确保跟 mysql 一个网络就好。

同时新增了几个 -e 参数,代表指定要连接的mysql的环境变量。

查看日志 docker logs <container-id> , 会看到如下内容:
# 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
打开网页 http://localhost:3000 新加几个 todo。 运行命令连接到 mysql :
docker exec -it <mysql-container-id> mysql -p todos

运行如下命令查看是否有新增的数据:

mysql> select * from todo_items;

Docker compose

启动了 todo listmysql 之后,在 docker-desktop 面板上看到的是两个容器,但是两个容器是有联系的,会同时启动和关闭,那该怎么让两个容器联动起来呢?

这里就要用到 docker compose ,通过一个 YAML 文件,可以同时启动和关闭两个容器,或者叫一组相关容器。

在项目的根目录下创建一个 docker-compose.yml 文件,跟最开始的 Dockerfile 同一目录下。 文件内容如下:
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:
删除刚才创建的 getting-startedmysql 两个容器。 在项目根目录下运行如下命令启动这组容器:
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

注意到 volumenetwork 都创建了,即使 network 没有指定。这是因为默认情况下,compose 会创建一个公共的 network

接着可以用 docker-compose logs -f 命令看下日志。 刷新下浏览器,现在可以正常使用了。然后可以观察下 docker-desktop 面板,可以看到两个容器放在了一起。 然后可以通过 docker-compose down 命令或者直接在面板上点击关闭这组容器。

总结

因为要搭建环境,将 docker 官网的内容整理简化,记录出来。