什么是 Docker?
让部署 Server 像手机安装 App 一样简单
过去当你写完了 Server 想要部署到服务器上,需要在服务器上配置一套运行环境,一旦服务器上环境配置失败,还可能出现整个服务挂掉的情况。
Docker 正是用 Linux 里叫做 “容器” 的技术实现了这个特性,通过容器把你的服务需要的环境和代码都打包进去,保证可以正常运行,当你需要部署到 Server 上的时候,只需要服务器上下载这个容器,然后运行就可以了。
编译,打包成容器,分发
利用容器的这个原理,各种服务,博客,网站,论坛,都可以使用由别人制作好容器,你再安装到自己的服务器上,容器之间互不干扰,干净整洁,大大简化了维护和配置成本。
什么是 “容器” ?
技术上来讲,容器的实现比较复杂也是一种虚拟化技术,但是概念非常好理解。
拿 iOS 的 App 打个比方,一个 iOS App 要运行起来,需要四部分
- iOS 内核(Darwin)
- 系统提供的 Frameworks
- 各种依赖的第三方 Frameworks
- 程序自己
当你编译出来一个 App 的时候,这个 App 相当于一个 容器
它包含了 各种依赖的第三方 Frameworks
和 程序自己
,而 iPhone 相当于你程序的宿主,提供了 iOS 内核
和 系统提供的 Frameworks
.
在 Linux 上,一个程序能跑起来,需要三部分
- Linux 内核
- 程序依赖的其他程序
- 程序自己
那么一个容器里,包含了 程序依赖的其他程序
和 程序自己
,内核则共用宿主的。
Linux 的容器环境和 iOS 运行 App 的 Sandbox 沙盒技术也非常相似,容器内无法直接访问外部,但外部则可以访问容器内。
安装 Docker
通过 Docker 官网 的 Get Docker 指南,可以轻松把 Docker 装在系统上,以下我将会以 Mac 为例。
在 Docker Store 里下载 Mac 版本的安装包
Mac 的内核是不支持容器技术的,因此 Mac 版本的 Docker 带了一个微型的 Linux 发型版,通过虚拟化技术运行在你的 Mac 上。
安装好之后运行,你的右上角就会出现一个小鲸鱼
![](https://img.haomeiwen.com/i119178/2863ecd93ab40754.png)
此时终端也可以执行 docker
命令了
➜ ~ docker -v
Docker version 17.03.0-ce, build 60ccb22
Hello World
现在你可以执行一个容器,看看个所以然
docker run hello-world
根据这个命令,Docker 会从仓库下载 hello-world 这个容器并执行,并在终端输出 hello world
。
如何构建一个容器?
构建容器前需要先构建容器镜像,分为两步
- 构建一个能够运行你的程序的环境
- 把程序放进去
那么第一步,就是从 Dockerfile 开始
编写 Dockerfile
Dockerfile 是容器镜像的描述文件,告诉 Docker 如何创建一个容器镜像。我们之前使用的 Vapor 框架也提供了一个可以运行 Vapor 的容器镜像,一起来看看它的 Dockerfile 是怎么写的
FROM vapor/vapor:1.0-xenial
# Check if we should install mysql header files to the container (Defualt: false)
ARG INSTALL_MYSQL=false
RUN if [ ${INSTALL_MYSQL} = true ]; then \
apt-get update && \
apt-get install -y libmysqlclient20 libmysqlclient-dev && \
rm -r /var/lib/apt/lists/* \
;fi
# Check if we should install postgres header files to the container (Defualt: false)
ARG INSTALL_PGSQL=false
RUN if [ ${INSTALL_PGSQL} = true ]; then \
apt-get update && \
apt-get install -y libpq-dev && \
rm -r /var/lib/apt/lists/* \
;fi
# Check if we should install sqlite header files to the container (Defualt: false)
ARG INSTALL_SQLITE=false
RUN if [ ${INSTALL_SQLITE} = true ]; then \
apt-get update && \
apt-get install -y libsqlite3-dev && \
rm -r /var/lib/apt/lists/* \
;fi
# Set work dir to /vapor
WORKDIR /vapor
EXPOSE 8080
这一大坨其实就是四步,也是构建一个容器镜像最基本的四步
第一步,设定基础容器
FROM vapor/vapor:1.0-xenial
意思是基于 vapor/vapor:1.0-xenial
这个容器镜像来构建,像编程中类的继承关系一样,容器镜像也可以继承,这样的好处是每个容器镜像只需要维护好自己的那部分,分发和保持健壮性更方便。
Docker 在构建时,会自动去本地和 Docker 云端的仓库里寻找被 FROM 指定的镜像。
第二步,安装必要的依赖
# Check if we should install mysql header files to the container (Defualt: false)
ARG INSTALL_MYSQL=false
RUN if [ ${INSTALL_MYSQL} = true ]; then \
apt-get update && \
apt-get install -y libmysqlclient20 libmysqlclient-dev && \
rm -r /var/lib/apt/lists/* \
;fi
#### Check if we should install postgres header files to the container (Defualt: false)
ARG INSTALL_PGSQL=false
RUN if [ ${INSTALL_PGSQL} = true ]; then \
apt-get update && \
apt-get install -y libpq-dev && \
rm -r /var/lib/apt/lists/* \
;fi
# Check if we should install sqlite header files to the container (Defualt: false)
ARG INSTALL_SQLITE=false
RUN if [ ${INSTALL_SQLITE} = true ]; then \
apt-get update && \
apt-get install -y libsqlite3-dev && \
rm -r /var/lib/apt/lists/* \
;fi
父级的容器镜像必然缺少部分你所需要的环境,因此你可以在这一步通过 RUN
命令来进行一些依赖安装。
第三步,设定工作目录
# Set work dir to /vapor
WORKDIR /vapor
我们将容器内的 /vapor
目录作为工作目录,意味着如果你 ssh
进入这个容器,会直接进入到这个目录,容器将把这个文件夹当作各种命令执行的根目录。
第四部,设置访问端口
EXPOSE 8080
意味着容器会开启 8080 端口的外部访问,宿主机器运行这个容器的时候,可以使用 -p xxxx:8080
的方式,将宿主机器的端口和容器的端口连接起来。
这样当用户在网络上访问宿主机器的 xxxx
端口的时候,就会变成通过 8080
端口访问容器。
此时,如果容器内的 Server 监听的是 8080
端口,那么服务器就可以接收到请求啦。
构建自己的 Dockerfile
首先,在终端进入到 vapor 项目根目录,创建一个 Dockerfile。
为了跑起来 Vapor,我需要按照以下思路构建一个 Dockerfile
- 选一个有 Swift 运行环境的父容器镜像
- 将 Swift 项目源码拷贝到工作目录
- 配置编译项目的环境并编译
- 设定访问端口 8000
# 选一个有 Swift 运行环境的容器镜像 vapor/vapor:latest
FROM vapor/vapor:latest
WORKDIR /vapor
# 将 Swift 项目源码拷贝到工作目录
ADD . /vapor
# 配置编译项目的环境并编译
RUN apt-get update && apt-get install -y openssl libssl-dev --no-install-recommends && \
vapor build && \
apt-get remove -y libssl-dev && \
rm -rf /var/lib/apt/lists/*
# 设定访问端口 8000
EXPOSE 8000
现在执行 docker build . --tag sudongpo_image
Docker 就会先根据 FROM
引用的镜像启动一个容器作为编译环境,然后逐条执行当前目录里的 Dockerfile
里的命令,执行完毕后,会输出一行信息
Successfully built sudongpo
在执行完 Dockerfile 退出的时候,Docker 会把当时的容器状态进行快照,变成一个不可改动的东西,叫做 容器镜像
,并命名为 sudongpo_image
。
容器镜像是什么?
那么镜像和容器什么关系呢?
容器镜像相当于手机上的 ROM,是只读的部分,当容器创建的时候,这部分会变成容器的 Base Layer ,就像是一个锁死的 Sketch 图层,一个灰色的圆。
![](https://img.haomeiwen.com/i119178/7a559b2e2b048985.png)
同时,Docker 会为容器创建一个可读写层,就像覆盖在被锁死的图层上的另外一个图层
![](https://img.haomeiwen.com/i119178/2ebd03976df99ba4.png)
在容器运行的时候,虽然你看起来可以修改任何东西,使得图片变成了下图的模样
[图片上传失败...(image-15e637-1588481042161)]
但本质上来讲,底层灰色的圆圈,没有被改动,只是表现层,即运行态的层面被改动了
![](https://img.haomeiwen.com/i119178/fe14c5e7b9837b4f.png)
这些变动会被 Docker 存储在 /var/lib/docker/aufs/diff/xxxxx_hash/
里(根据 Docker 的配置不同,路径也会有所不同)。
我们往下看,来继续梳理两者的关系。
现在,你可以使用 docker run --name sudongpo -p 8000:8000 sudongpo_image vapor run
来创建并运行一个容器。
被隐藏的细节
docker run
其实是将 docker create
和 docker start
两个命令合并在了一起。
这行命令做了以下几件事
- 使用
sudongpo_image
这个容器镜像作为基础运行环境创建容器 -
--name sudongpo
将容器命名为sudongpo
-
-p 8000:8000
映射宿主的8000
端口到容器的8000
端口 -
vapor run
当容器启动后,在工作目录执行vapor run
命令 - 将
-p 8000:8000
vapor run
作为容器的配置项保存起来,每次启动容器的时候都按照这个配置进行启动 - 运行容器
自此,你可以使用 docker start sudongpo
来开启这个容器,或者使用 docker stop sudongpo
来关闭这个容器,容器启动的时候,会自动完成端口映射,并执行 vapor run
。
网友评论