docker快速入门:20分钟学会用 docker部署服务

看到这篇文章的读者肯定希望快速上手 docker,所以这篇文章不会涉及到 docker的底层原理,而是主要讲如何编写 Dockerfile以及如何使用 docker进行项目部署,不管怎么样我们先用起来。不过在这之前我们先简单介绍一下 docker以便确定你是否真的需要使用 docker。

首先我们先了解一下 docker能够帮我们做什么。用一句话概括就是:docker可以将我们的项目打包,然后无障碍地部署到大部分服务器上。docker本身可以运行在大部分系统上,但使用 docker部署的项目必须基于 linux系统。也就是说我们可以在 windows上用 docker部署运行在 Linux上的项目,但是我们没办法在 windows上用 docker部署运行在 windows上的项目,当然在 Linux上也不行。

在手动部署项目的时候我们通常会先把代码上传到服务器、然后安装相关依赖、配置环境、最后启动项目。通常这些过程都需要我们手动敲入命令,每次部署的时候都需要重新敲一遍。而 docker做的工作就是将这些命令记录下来,在部署的时候再逐个执行。

看到这里可能会有人问:这和我写的部署脚本有什么区别?如果 docker仅仅只做这些事的话,那真的是没有啥区别。但是 docker还能够保证每次运行的环境都是一致的,并且与宿主机的环境进行隔离。相信看到这里大家已经能够 get到 docker的妙用了。

1 Dockerfile

前面讲到了 docker通过记录执行的命令来保证每次部署时执行的命令都一致,Dockerfile就是保存这些命令的文件。docker会根据 Dockerfile来构建环境并生成一个镜像,最后通过运行镜像来完成项目部署。

想要快速上手 docker我们需要了解下面这几个命令:FROM, MAINTAINER, ENV, ADD, COPY, EXPOSE, ENTRYPOINT, WORKDIR, RUN, CMD等。下面我们对他们进行逐个介绍。

1.1 FROM

FROM用来指定基础镜像。可以理解为所需要的的操作系统,不过与操作系统不同的是 FROM 指定的镜像通常会配置好我们所需要的的环境。比如下面这行代码就指定了带有 python 环境的镜像,我们可以在构建好的镜像中直接使用 python 而不需要额外安装。

FROM python:3.6

1.2 MAINTAINER

MAINTAINER 指定了 Dockerfile的作者,或者说维护者。可填可不填。

1.3 ENV

ENV用来设置环境变量。示例如下:

ENV PRODUCTION_ENV 1

1.4 ADD

ADD 命令可以用来向镜像中添加我们需要的文件,比如配置文件、代码、资源文件等等。ADD 中所使用的的路径是 Dockerfile所在目录的相对路径,也可以使用 URL,如果目标文件是一个 tar压缩包的话会被自动解压成目录。示例如下,其中第一个参数是要添加的文件,第二个参数是镜像中对应的文件,如果不存在会自动创建:

ADD ./database.ini /root ADD ./src /root ADD/root ADD code.tar /root

1.5 COPY

COPY 的功能和 ADD 相似,不过 COPY 只能复制本地的文件到镜像中,并且不会自动解压 tar压缩包。

1.6 EXPOSE

EXPOSE 声明容器中要使用的端口,仅仅做声明,没有实际作用。运行时使用 -p 容器端口:宿主机端口 指定端口映射。

EXPOSE 80 EXPOSE 80 8080

1.7 ENTRYPOINT

容器启动时执行的命令,并且不会被 docker run 的参数覆盖。如果指定了多个 ENTRYPOINT 只有最后一个会被执行。命令的格式如下:

ENTRYPOINT ["可执行文件", "参数1", "参数2"]ENTRYPOINT 命令 参数1 参数2

1.8 WORKDIR

WORKDIR 指定工作目录。后续的所有命令都会在这个目录下面执行。

1.9 RUN

RUN 表示要执行的命令,比如我们在 /root/project 下面有一个 requirements.txt 文件,保存了我们项目的依赖,下面的命令可以让我们在构建镜像时安装依赖:

WORKDIR /root/project RUN pip isntall -r requirements.txt

1.10 VOLUME

VOLUME 声明容器中的挂载点,在运行的时候通过 -v 绑定到宿主机目录,注意需要提供完整的目录路径。

也可以用下面的方法在 Dockerfile中指定,但是这种方法绑定的目录是以 docker的安装目录加上容器 ID为根目录的,也就是不能指定具体的目录,如果想指定具体的目录必须在运行时通过 -v 绑定。

VOLUME ["/data", "/data"]

上面的 /data 可能在宿主机上的路径为 d:/docker/一长串容器ID字符/data。

2 实践

前面命令讲了那么多,现在我们来实践一下。我们先来创建一个项目,目录结构如下:

httpServer/ │log/ │http.py └─ Dockerfile

其中我们在 http.py 中写了一个简单的 HTTP服务器,这个服务器在接收到请求时会将请求报文的第一行保存到 log.txt 中,并返回 this is docker server!。为了防止重复部署时 log.txt 中的内容丢失,我们使用 VOLUME 声明一个挂在点,这样就可以将 log.txt 保存在宿主机上。

http.py 中的内容如下:

import socket from multiprocessing import Process def handle_client(client_socket): # 解析请求报文 request_data = client_socket.recv(1024) request_lines = request_data.splitlines() request_start_line = request_lines[0] # 打开日志文件写入日志 这个日志文件保存在宿主机上 with open(./log/log.txt, ab) as f: f.write(request_start_line + b\n) # 构建响应 response_start_line = "HTTP/1.1 200 OK\r\n" response_headers = "Server: docker server\r\n" response_body = "The is docker server!" response = response_start_line + response_headers + "\r\n" + response_body # 向客户端返回响应数据 client_socket.send(bytes(response, "utf-8")) # 关闭客户端连接 client_socket.close() if __name__ == "__main__": server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.bind((0.0.0.0, 8000)) server_socket.listen(128) while True: client_socket, client_address = server_socket.accept() handle_client_process = Process(target=handle_client, args=(client_socket,)) handle_client_process.start() client_socket.close()

下面我们来编写 Dockerfile:

# 指定带有 python 3.6环境的镜像 FROM python:3.6 # 作者 MAINTAINER geebos # 将当前目录所有文件添加到要构建的镜像中 COPY . /root/httpServer # 声明容器中的 /root/httpServer/log 目录为挂载点 VOLUME /root/httpServer/log # 设置工作目录 WORKDIR /root/httpServer # 绑定端口 EXPOSE 8000 # 设置启动命令 ENTRYPOINT python http.py

上面这些文件都创建好之后我们开始构建镜像:

首先运行 docker build -t http-server . 构建镜像。 - 其中 -t http-server 指定了镜像的名字为 http-server,我们也可以这样设置来指定镜像的版本 -t http-server:1.0,不过如果我们指定了版本号的话,在启动容器的时候也需要带上版本号,否则会找不到镜像。

然后运行 docker run -id -v d:/dockerTest/httpServer/log:/root/httpServer/ log -p 8000:8000 --name http-server-container http-server 来启动容器。其中 :

-d 表示后台启动--name http-server-container 指定容器的名称为 http-server-container-v d:/dockerTest/httpServer/log:/root/httpServer/log绑定挂载目录-p 8000:8000 指定端口映射。

启动之后我们可以访问 :8000。访问之后查看 d:/dockerTest/httpServer/log/log.txt 中会发现我们的请求记录被保存到宿主机上了。

我们可以使用 docker exec -it http-server-container /bin/bash 进入到容器内部。也可以试着更改 Dockerfile中的参数看看有什么效果。

3 常用 docker命令

docker build:构建镜像。常用格式:docker build -t 镜像名 Dockerfile所在路径。示例: dcoker build -t image-name .。docker run:基于镜像启动容器。常用格式:docker run -id -t --name 容器名 镜像名/镜像ID。示例:docker run -id -t --name container-name image-name。docker ps:显示所有容器信息。docker images:显示所有镜像信息。docker stop:停止容器运行。docker start:重新运行一个已停止的容器。docker rm:删除容器,删除之前要先确保容器已经停止运行。可以指定多个容器。示例:docker rm container-name。docker rmi:删除镜像,删除之前要确保没有基于该镜像的容器存在。可以指定多个镜像。示例:docker rmi image-name/image-id docker exec:在容器中执行命令。示例:docker exec -it container-name /bin/bash。这命令可以启动容器内的 bash,其中 -i 表示以交互的方式运行命令,`-t`表示在终端(tty)中运行。

4 docker部署常用流程

上传项目文件到服务器进入项目文件夹运行 docker build -t image-name:version . 构建镜像,强烈建议指定版本号,这样在部署出错的情况下可以快速回滚。示例:docker build -t server:v1 . 在运行容器之前先停止老版本的容器释放端口等资源,docker stop server-container:v0 镜像构建完成之后再运行 docker run -d --name container-name:version image-name:version 示例:docekr run -d --name server-container:v1 server:v1确定服务正常运行之后删除之前版本的镜像和容器,如果服务器空间足够的话可以先不删除镜像,只删除容器。docker rm server-container:v0 docker rmi server:v0 如果在部署后发现有问题需要恢复之前的版本docker stop server-container:v1 docker run -d --name server-contaienr:v0 server:v0

结束语

相信看完这一片文章之后大家都能够愉快地使用 docker了,但是对其中的原理和相关概念可能还不怎么了解。比如容器、镜像是什么?docker如何与宿主机做到环境隔离?docker和虚拟机的区别是什么?等等。这些问题我有些也是一知半解,不过我会找时间去搞清楚然后继续更新。