Docker初探
一. 镜像&容器
1. 容器
容器是被隔离的进程
A container is simply another process on your machine that has been isolated from all other processes on the host machine.
2. 镜像
由于容器是一种进程,所以镜像是容器的文件系统。此外,镜像还包含容器的一些配置文件
When running a container, it uses an isolated filesystem. 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.
二. 存储管理
一个镜像可以对应多个容器,每个容器都会有一个暂存空间(scratch space),但是这个空间对于每个不同的容器来说,都是隔离的,即使是对于同一个镜像下的其他容器来说也是一样的。
docker主要提供了两种方法,可以将数据存储到主机的文件系统里面,一种是volume,一种是bind mount。
无论哪一种方法,容器都将其试作一个目录/文件(这取决于设置)。
1. volume
volume在主机中的真实地址由docker来掌控(“/var/lib/docker/volumes/”),我们只需要通过docker建立一个volume,并将volume挂在到容器的运行空间当中即可。
创建volume
docker volume create your-volume-name
挂载volume到容器上
在容器运行时,将volume挂载到容器的运行空间
docker run -d -p 3000:3000 -v your-volume-name:/your/path your-image-name
查询volume
docker volume ls
2. bind mount
bind mount是将根据主机的绝对路径,将目录/文件挂载到容器的运行空间的。(如果容器的挂载目录原先下方有内容的时候,将被这种挂载隐藏,显露的是主机中的内容)
docker run -d \
-it \
-v path-in-host:path-in-container \
your-image-name
这种方式的好处在于,通过绝对路径的挂载,可以直接在主机系统里进行操作,这种改变将直接反应到容器身上。也就是说,如果想对镜像做测试性改变而不生成新的镜像的时候,就可以使用这种方法,
3. 其他
其他更详细内容见官网:https://docs.docker.com/storage/
三. 网络配置
通常来说,每个容器应该只负责一个功能,代表一个服务(单一职责)。
each container should do one thing and do it well
Running multiple processes will require a process manager (the container only starts one process), which adds complexity to container startup/shutdown
因此对于一个应用来说,有可能需要使用多个容器,也就意味着容器与容器之间需要进行通信。
由于每个容器都是隔离的,因此容器间的通信,需要使用网络(Container networking)。
If two containers are on the same network, they can talk to each other. If they aren’t, they can’t.
1. birdge networks
网桥这种通信方式用于运行在同一主机的容器之间。如果是不同主机的容器,需要使用其他的通信方式。
在docker开启的时候,会自动创建一个默认网桥(bridge),所有的容器如果没有特殊配置,都会自动连接到这个网桥上。但是用户也可以自定义网桥(user-defined bridge)
自定义网桥 vs 默认网桥
- 自定义网桥可以定义域名(DNS),使通信的时候更为方便;默认网桥需要使用-link才能使用域名
- 自定义网桥提供更好的隔离。默认网桥可能导致不相关的容器可以通信。
- 自定义网桥可以单独配置,如MTU、IP规则等,而默认网桥配置后影响全部,并且需要重新启动docker
- 可以在运行过程中加入和退出自定义网桥,而默认网桥不行
- 通过-link在默认网桥上通信时,环境变量是共享的。
创建自定义网桥
docker network create net-name
容器使用自定义网桥的方式
-
在创建时候进行连接
docker create --name my-nginx\ --network net-name\ --network-alias service-name\ --publish 8080:80\ nginx:latest
--network-alias相当于域名,其他在net-name这个网络上的服务,可以通过DNS与这个容器进行通信
-
运行时进行连接
docker network connect net-name my-nginx
2. overlay networks
之前的网桥是的作用在于只能使同一个docker下的不同容器进行通信,而overlay networks可以使不同docker 下的容器进行通信。
The
overlay
network driver creates a distributed network among multiple Docker daemon hosts.
overlay network是基于docker swarm进行管理的,当创建一个swarm的时候,两个网络将被建立。
一个是overlay network,用于连接每个容器节点。
一个是bridge network,用于连接两个docker主机。
- an overlay network called
ingress
, which handles control and data traffic related to swarm services. When you create a swarm service and do not connect it to a user-defined overlay network, it connects to theingress
network by default.- a bridge network called
docker_gwbridge
, which connects the individual Docker daemon to the other daemons participating in the swarm.
创建overlay network
-
使用overlay network之前,需要先使docker处于某个swarm管理下
docker swarm init
将本主机配置为swarm管理节点(a swarm manager )
docker swarm join
加入swarm,可以以manager/worker的身份进行加入
-
加入到某个swarm后,docker已经创建了一个默认的overlay网络。现在,可以创建自定义的overlay network
docker network create --driver overlay overlay-name
--driver可以简写为-d,默认为网桥网络。
3. 其他
见官网:https://docs.docker.com/network/
四. Docker Compose
现在已经知道了存储管理和网络配置几个重要模块,下面尝试建立一个多容器的应用。
本模块需要用到官网上提供的getting-started项目代码:download
这是一个todo-list的小程序,在下载的项目都app文件下,创建Dockerfile
文件,并输入以下内容:
FROM node:12-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]
使用命令docker build -t getting-started .
创建镜像。镜像创建成功后,可以通过docker images
进行查看。
这个时候我们就有一个名为getting-started
的镜像了(Dockerfile是用来指导docker如何创建镜像的文件,先忽略它具体怎么编写)。
1. 创建多容器应用——基础方法
1.1 容器通信(网络配置)
假设我们现在有一个网站服务,还有一个数据库服务,分布部署在不同的容器内。我们现在所需要做的就是,使网站能够将获得的数据存到数据库当中。
根据前面的网络配置内容,我们需要先建立一个网络,使两个容器通信
docker network create todo-app
接下来将mysql服务跑起来,我们使用之前创建的网络todo-app,使用别名(域名)mysql,并配置了环境变量(-e)
docker run -d\
--network todo-app\
--network-alias mysql\
-e MYSQL_ROOT_PASSWORD=secret\
-e MYSQL_DATABASE=todos \
mysql:5.7
然后将网页服务跑起来,需要暴露端口(-p),使用todo-app网络,配置环境变量(-e)
docker run -d\
-p 3000:3000\
--network todo-app\
-e MYSQL_HOST=mysql \
-e MYSQL_USER=root \
-e MYSQL_PASSWORD=secret\
-e MYSQL_DB=todos\
getting-started
打开网页http://localhost:3000/后,向里面添加内容。
之后进入容器mysql查询数据库
docker exec -it <mysql-container-id> mysql -p
可以看见相应的数据:
mysql> select * from todo_items;
+--------------------------------------+------+-----------+
| id | name | completed |
+--------------------------------------+------+-----------+
| 0f451c2b-1fba-43c4-925c-9d17b4b38d25 | try | 0 |
| 5d81e107-4350-4c47-9da5-ed99b528c06c | next | 1 |
+--------------------------------------+------+-----------+
2 rows in set (0.00 sec)
1.2 数据持久化存储(存储管理)
现在,使用前面的方式已经能够使这个应用跑起来了。但是此时如果我们移除mysql对应的容器,再重新创建一个运行mysql的容器,可以发现之前的数据全部都没了。
因此,这就是用到存储管理的部分了。我们要求数据能够持久化存储。
我们使用命名卷(volume)创建mysql容器:
docker run -d\
--network todo-app\
--network-alias mysql\
-e MYSQL_ROOT_PASSWORD=secret\
-e MYSQL_DATABASE=todos \
-v todo-mysql-data:/var/lib/mysql \
mysql:5.7
这段命令与之前唯一不同的就是-v todo-mysql-data:/var/lib/mysql
,我们将卷todo-mysql-data
映射到了容器/var/lib/mysql
。
此时,如果关闭现在这个mysql容器,同时再用同样的volume创建新的mysql容器,数据是不会变化的。
2. 创建多容器应用——Docker Compose
之前的步骤,能够使多容器应用通过网络运行起来,也能够保证数据持久化存储。然而,命令好麻烦,我们可以使用Docker组件来“打包”这些命令。
打开之前下载的app文件夹,创建文件docker-compose.yml
,输入:
version: "3.8"
services:
app:
image: getting-started
ports:
- 3000:3000
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:
版本号与docker内核对应。如果和之前的命令行去对应:
docker run -d\
-p 3000:3000\
--network todo-app\
-e MYSQL_HOST=mysql \
-e MYSQL_USER=root \
-e MYSQL_PASSWORD=secret\
-e MYSQL_DB=todos\
getting-started
docker run -d\
--network todo-app\
--network-alias mysql\
-e MYSQL_ROOT_PASSWORD=secret\
-e MYSQL_DATABASE=todos \
-v todo-mysql-data:/var/lib/mysql \
mysql:5.7
可以发现,唯一的区别就是,命令行还声明了network,而使用组件的方式,却没有明确指出使用哪个网络。这是因为,使用组件后,会自动创建一个网络。
在app文件下使用命令:
/app tianqizhao$ docker-compose up -d
Creating network "app_default" with the default driver
Creating app_mysql_1 ... done
Creating app_app_1 ... done
如果想要关闭组件,则使用:
docker-compose down
五. tips
在开发过程中,如果每次修改文件都要build一次镜像好像还挺麻烦的。可以通过bind mount的方式,将主机的文件夹映射到容器内部的文件夹,再直接运行就行了。
docker run -d\
-p 3000:3000\
--network todo-app\
-e MYSQL_HOST=mysql \
-e MYSQL_USER=root \
-e MYSQL_PASSWORD=secret\
-e MYSQL_DB=todos\
-v "$(pwd):/app" \
-w /app\
node:12-alpine\
sh -c "yarn install && yarn run dev"
如果和之前写过的Dockerfile进行对比,发现不过是把一些命令移出来了。
-v "$(pwd):/app"
表示,将当前文件夹绑定映射到容器/app
文件夹。 -w表示sh -c "yarn install && yarn run dev"
运行的路径(workspace)。
使用这个命令,一旦改变app文件夹内的内容,就不需要重新build镜像,直接使用该命令运行即可。