这里首先介绍使用 Dockerfile + docker-compose 的方法。该方法最重要的是了解该项目的环境需求,接着就是书写 docker-compose.yml 文件即可。( yaml 语法参考这里

这里用一个例子来进行简单的讲解,其中包含一个我们项目最常用到的 Python + Mysql 的配置,其他包含更多镜像的配置过程类似。

1 配置 Python 镜像

这里我们以一个简单的 flask 应用为例。以下默认为 python3 环境,并 pip 工具代表 pip3。

1.1 导出 Python 依赖环境

这里可以使用 pip 自带的 freeze 功能,导出需要的环境列表。

# 导出环境
pip freeze > requirements.txt

上述命令需要配合虚拟环境才好用,不然可能会导出所有的环境依赖包。这里也可以用 Python 的一个第三方库——pipreqs

# 安装 pipreqs
pip install pipreqs
# 环境导出
pipreqs ./
# Windows下环境导出
pipreqs ./ --encoding=utf8

以上命令需要在项目的根目录中运行,同时当在 Windows 环境下进行使用时,需要添加 --encoding 不然可能出现 (UnicodeDecodeError: ‘gbk’ codec can’t decode byte 0xa8 in position 24: illegal multibyte sequence) 这样的错误。

1.2 应用测试

这里我们假设是 flask 应用,我们使用以下命令测试应用程序。并访问下述地址 http://localhost:5000 默认端口为5000。

# 测试应用
python3 -m flask run

这时切换回运行我们的服务器的终端,服务器日志中会显示如下请求。其中数据和时间戳将有所不同。

127.0.0.1 - - [22/Sep/2020 11:07:41] "GET / HTTP/1.1" 200 -

1.3 创建 Dockerfile

这里首先给出完整的 Dockerfile 。之后再进行一一的解释。( Dockerfile语法

FROM python:3.8-slim-buster

WORKDIR /app

COPY requirements.txt requirements.txt
RUN pip3 install -r requirements.txt

COPY . .

CMD [ "python3", "-m" , "flask", "run", "--host=0.0.0.0"]

Docker 支持镜像的继承,因此我们可以首先在官方提供的、已有的 Python 镜像上面进行配置,这里 From 后面跟得是您需要的基本镜像源(默认从官方仓库下载)。

FROM python:3.8-slim-buster

为了使运行其余命令更容易,让我们创建一个工作目录。这指示 Docker 使用此路径作为所有后续命令的默认位置。这样,我们不必键入完整的文件路径,而是可以根据工作目录使用相对路径。

WORKDIR /app

为了实现本地文件存入镜像,这里使用 COPY 指令来传入,该命令有两个参数,第一个是我们要放入镜像的文件,第二个是存入的地方。由于我们制定了 WORKDIR 因此将会默认目录地址为 /app。

# 负责当前目录下的 requirements.txt 到镜像 /app/requirements.txt
COPY requirements.txt requirements.txt
# 负责当前目录下的所有文件到镜像 /app
COPY . .

dockerfile 提供了两种命令执行的方式,分别为 RUN 和 CMD,这两种方式的区别在于 RUN 是创建镜像时执行的命令,CMD 是当镜像运行为容器时才会执行的命令。

# 构建镜像时完成 python 依赖环境包的安装
RUN pip install -r requirements.txt
# 容器运行时启动服务
CMD [ "python3", "-m" , "flask", "run", "--host=0.0.0.0"]

1.4 镜像建立

现在我们创建了 Dockerfile ,接下来使用 docker build 就可以建立对应的镜像。

docker build --tag python-docker .

其中 --tag 参数为新建镜像的名字, . 为 dockerfile 的相对地址(也可以使用绝对地址)

1.5 镜像目录结构

python-docker
|——project_files
|——requirements.txt
|——Dockerfile

1.6 后续操作

可以查看当前本地镜像,接着给镜像标记名称等操作。

# 查看本地镜像
docker images
# 加 Tag
docker tag python-docker:latest python-docker:v1.0.0

2 Python 容器运行测试

在第一个流程中以及完成了我们项目 Python 镜像的封装,接下来我们可以对我们封装好的镜像进行测试,这里我们需要将其运行为对应的容器。

docker run python-docker

但由于我们运行的应用程序时 REST 服务器,因此并不会有其他的输出,而是在循环中运行以等待传入的请求,而不会将控制权交还给 OS ,直到容器停止为止。

因此这时候我们可以新开一个终端,并使用 curl 指令向服务器发送 GET 请求。

curl localhost:5000

但此时可以会返回连接拒绝的消息,这是因为我们的容器、包括其网络是独立运行的,因此需要将其发布到本地的 5000 端口。

docker run --publish 5000:5000 python-docker

此时我们打开新的终端,重新使用 curl 命令,就可以连接成功。

2.1 后台运行

Web 程序不需要我们进行交互,因此我们可以在后台运行。其中 -d 为后台运行,-p 为端口映射。

docker run -d -p 5000:5000 python-docker

2.2 容器相关操作

  • docker ps:列出运行的容器,这里容器可以理解为进程;
  • docker ps -a:列出所有容器;
  • docker stop docker_ID:停止容器;
  • docker restart docker_ID:重启容器;
  • docker rm docker_ID:删除容器;
  • docker run docker_ID:启动容器;

3 配置 MySQL 及网络

容器中运行数据库,我们需要创建几个卷( volume )来实现本地持久化存储数据和配置。同时我们要实现应用程序和数据库两个容器的交互,我们需要建立一个网络,来实现两个容器的互联。

3.1 创建卷和网络

# 创建卷(卷名自定义)
docker volume create mysql
docker volume create mysql_config
# 创建网络(网络名称自定义)
docker network create mysqlnet

3.2 运行 MySQL

现在我们可以在容器中运行 MySQL ,并将其附加到我们创建的卷和网络上。

docker run --rm -d -v mysql:/var/lib/mysql \
-v mysql_config:/etc/mysql -p 3306:3306 \
--network mysqlnet \
--name mysqldb \
-e MYSQL_ROOT_PASSWORD=p@ssw0rd1 \
mysql

其中参数含义如下:

  • –network:指定网络;
  • –name:为容器指定一个名称
  • -p:指定端口映射,格式为:主机(宿主)端口:容器端口
  • -v(–volume):指定卷
  • -e:设置环境变量;
  • –rm:退出时就删除容器,一般用于临时测试;

3.3 应用程序及 MySQL 的连接

该部分应该由 Python 代码中写入,类似于以下文件。

# 连接示例
import mysql.connector
import json
from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
return 'Hello, Docker!'

@app.route('/widgets')
def get_widgets() :
mydb = mysql.connector.connect(
host="mysqldb",
user="root",
password="p@ssw0rd1",
database="inventory"
)
cursor = mydb.cursor()


cursor.execute("SELECT * FROM widgets")

row_headers=[x[0] for x in cursor.description] #this will extract row headers

results = cursor.fetchall()
json_data=[]
for result in results:
json_data.append(dict(zip(row_headers,result)))

cursor.close()

return json.dumps(json_data)

@app.route('/initdb')
def db_init():
mydb = mysql.connector.connect(
host="mysqldb",
user="root",
password="p@ssw0rd1"
)
cursor = mydb.cursor()

cursor.execute("DROP DATABASE IF EXISTS inventory")
cursor.execute("CREATE DATABASE inventory")
cursor.close()

mydb = mysql.connector.connect(
host="mysqldb",
user="root",
password="p@ssw0rd1",
database="inventory"
)
cursor = mydb.cursor()

cursor.execute("DROP TABLE IF EXISTS widgets")
cursor.execute("CREATE TABLE widgets (name VARCHAR(255), description VARCHAR(255))")
cursor.close()

return 'init database'

if __name__ == "__main__":
app.run(host ='0.0.0.0')

3.4 连接测试

docker run \
--rm -d \
--network mysqlnet \
--name rest-server \
-p 5000:5000 \
python-docker

运行起我们的容器过后,可以使用我们的 curl 工具进行测试。测试样例如下,各个项目可能不大相同。

# 测试样例
curl http://localhost:5000/initdb

之后应该会返回相应的数据。如果有返回,那么就是连接成功了。