Loading

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 默认网桥

  1. 自定义网桥可以定义域名(DNS),使通信的时候更为方便;默认网桥需要使用-link才能使用域名
  2. 自定义网桥提供更好的隔离。默认网桥可能导致不相关的容器可以通信。
  3. 自定义网桥可以单独配置,如MTU、IP规则等,而默认网桥配置后影响全部,并且需要重新启动docker
  4. 可以在运行过程中加入和退出自定义网桥,而默认网桥不行
  5. 通过-link在默认网桥上通信时,环境变量是共享的。

创建自定义网桥

docker network create net-name

容器使用自定义网桥的方式

  1. 在创建时候进行连接

    docker create --name my-nginx\
     --network net-name\ 
     --network-alias service-name\
     --publish 8080:80\
     nginx:latest
    

    --network-alias相当于域名,其他在net-name这个网络上的服务,可以通过DNS与这个容器进行通信

  2. 运行时进行连接

    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 the ingress 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

  1. 使用overlay network之前,需要先使docker处于某个swarm管理下

    docker swarm init
    

    将本主机配置为swarm管理节点(a swarm manager )

    docker swarm join
    

    加入swarm,可以以manager/worker的身份进行加入

  2. 加入到某个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镜像,直接使用该命令运行即可。

posted @ 2020-12-28 12:08  有人找你  阅读(75)  评论(0编辑  收藏  举报