Docker使用及迁移

最近在折腾Docker,容器的理念非常先进,使用起来一个直观的感受就是,再也不用配麻烦的环境配半天了。

容器化是什么

为了解决“代码能在我的电脑上跑”的问题,可以将一个项目的一切包括运行环境依赖项、构建依赖项等打包成一个能在任何环境下运行的“集装箱”,当需要在别的设备上运行时,会自动根据配置获取依赖,无需手动操作。这些“集装箱”之间相互独立、互不影响,使得每一个项目都获取了很大的隔离性。

这样的过程,就是容器化,容器之间可以共享操作系统,因此相比于虚拟机而言更加轻量级,是迁移项目,部署多服务项目的优良选择。

三种实体

可能看到上面的内容,依然很迷惑。其实,只需要了解Docker最重要的三种实体就可以明白容器化到底在做什么。这三种实体是:Image,Container和Volume。从实际的效果上来说,可以认为Image是系统映像,Container是运行在系统上的实际操作系统,Volume就是文件系统中的根目录及下面的文件。

Image是静态的文件集合,它包含了运行时需要的所有内容,通过DockerFile构建得到的对象就是Image,相当于Docker系统内的“exe文件”。Image在构建之后就不会被修改,以C#控制台程序为例,我们在源码中加入一个DockerFile并构建,就是先进行编译,然后将得到的文件封装成整体为Image,之后就可以运行这个Image,通过指令调用来正常的使用这个控制台程序。除了自己编译之外,Docker社区中也存在着大量的镜像Image比如PostgreSQL镜像,可以直接下载并运行,无需额外的操作。

Container是基于Image的运行时整体,可以认为有自己的操作系统,执行着文件系统里面的内容,同时有着自己的内部网络和端口,相当于“运行时的进程”。比如说PostgreSQL镜像运行起来的Container就等同于一个SQLServer,只不过Container是不存在离线状态的,也就是说无论启动多少次,Container的运作方式都是一样的。

Volume是数据卷,用于持久化容器的运行时数据,数据卷对容器是独立的,一个数据卷可以挂载到任何一个容器中,就像移动U盘一样。

那么一个项目的迁移部署流程就是:下载源码和DockerFile,Build生成Image,或者直接下载Image,然后基于Image构建Container,之后的效果就如同本地测试一般。

Docker-Compose

由于每一个Container都可以视为一个独立的主机,将物理端口映射为自己的虚拟端口。

为了将多个Container聚合在一起,并能够相互之间方便的交流,使用Docker-Compose工具可以利用持久化文件来管理多个Container,而不用每次一个一个使用docker指令运维,同时将一个项目内的所有Container放置在一个虚拟网络中,可以以http://{服务名}的方式互相访问。除此之外,docker-compose.yml文件还可以定义容器间的依赖关系以指定启动顺序、指定Image构建方式、提供环境变量和数据卷路径等配置,基本上满足了自定义部署的需求。

下面给出一个PostgreSQL镜像部署的例子:

services:
    db:
        image: postgres:latest
        restart: always
        environment:
          - POSTGRES_USER=${POSTGRES_USER}
          - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
          - POSTGRES_DB=${POSTGRES_DB}
        volumes:
          - postgres_data:/var/lib/postgresql/data

networks:
  xxx_network:
    driver: bridge

这里的${POSTGRES_USER}之类的参数由同目录下的.env文件定义,在DockerCompose文件中最好不要硬编码敏感字段。

容器调试

容器可以理解为一个独立主机,在需要调试的时候,就需要进入到容器网络中,或者进入到某个指定的容器中,这个时候就要使用docker exec指令。

docker exec -it xxx sh
docker exec -it xxx dotnet xxx.dll

上述指令可以进入指定的容器,并在容器内附加一个sh进程或者启动一个写好的C#控制台程序,此时可以理解为通过SSH连接方式进入了远程服务器内部并启动了一个沟通终端,可能有正在运行的Web服务器,也有可能有很多等待手动执行的测试脚本/程序。

端口映射

如果不进行端口映射,容器本身就是一个孤立的城堡,只能和网络内的其他容器沟通,而无法连接Internet。

首先要引入防火墙的概念,物理服务器并不会开放所有的端口,它只会对设定规则内的端口开放出入流量。

其次,容器如果想要监听某个物理端口,就需要显式地指出来,比如说8080:80,如果物理服务器在8080端口有流量输入,那么这个流量会自动转发到容器的80端口。同样,如果容器要回复,也是将流量发送至自己的80端口,然后转发到物理服务器的8080端口,最后发送到客户端。在客户端的角度,它认为自己一直是在和物理服务器进行交流,而无法得知具体执行的的转发和IP修改。

总而言之,端口映射解决的是容器和公网的连接,虚拟网络解决的是容器之间的连接。