Docker 常见使用手册

Docker 常见使用命令

镜像拉取

docker 获取镜像的方式非常简单,使用docker pull即可拉取对应的镜像文件。

格式为:docker pull [OPTIONS] NAME[:TAG|@DIGEST]

例如想拉取 mysql,就可以使用

1
docker pull mysql

也可以指定对应的版本例如 mysql 5.7

1
docker pull mysql:5.7

容器启动

常见的启动命令

下面是常见的一些启动命令参数,还有几个没写,也没用过,等用到的时候再记录补充。

参数 描述 使用频率
-d 在后台运行容器并打印容器ID
-it 交互运行容器,并分配一个伪tty。
–name 为生成的容器分配一个临时名称。
-p 配置容器端口与宿主机端口的映射
-v 配置挂载卷,尤其是数据库类的容器,使用挂载卷可以方便、永久的存储数据。
-e 配置容器环境变量,会将对应内容写入容器的系统环境变量中。
–rm 退出容器时,自动移除容器。
–link 将容器连接到另外一个容器,在多容器联合使用中比较常见。
–network 连接容器到指定网络。
–privileged 给容器扩展的权限。
–restart: 设置容器的重启策略。

挂载数据

在使用 mysql 或 redis 等数据库容器的时候,往往需要存储数据,但容器删除后对应的数据也会被删除,如果想存储其中的数据并永久保存,就可以使用挂载卷的方式,将数据库的数据存储到本地,在使用的时候只需要将数据挂载进去即可。

通常情况下,会在当前目录创建一个文件夹当做宿主机的挂载目录,例如:

1
mkdir -p ./mysql-volumn/mysql-data

这样就创建好了一个目录,存放对应的数据。

然后使用-v参数指定挂载目录即可。

1
docker run -it -d --name mysql -v ./mysql-volume/mysql-data:/var/lib/mysql mysql:5.7

其中除了挂载数据卷,也可以挂载对应文件,这里使用 redis 就可以挂载数据卷后,再将 redis.conf 也挂载进去。

1
docker run --name redis -p 6379:6379 -v ./docker-data/redis/redis.conf:/etc/redis/redis.conf -v ./docker-data/redis:/data -d redis

端口映射

如果想让宿主机上的应用也可以访问容器,就需要将容器中应用的端口进行映射,使用-p参数即可指定端口。

1
docker run -it -d --name mysql -v ./mysql-volume/mysql-data:/var/lib/mysql -p 3306:3306 mysql:5.7

设置环境变量

有时候我们希望在启动容器的时候将一些环境变量进行写入,就不需要后续再进入容器进行更改,例如 mysql 的密码,就可以在启动的时候使用-e参数进行配置。

1
docker run -it -d --name mysql -v ./mysql-volume/mysql-data:/var/lib/mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7

启动服务配置参数

这里启动 redis 的容器后,我希望使用命令加载配置文件,并执行参数,就可以使用下面命令。在启动 redis 后,再使用redis-server /etc/redis/redis.conf --appendonly yes启动服务设置参数。

1
docker run --name redis -p 6379:6379 -v ./docker-data/redis/redis.conf:/etc/redis/redis.conf -v ./docker-data/redis:/data -d redis redis-server /etc/redis/redis.conf --appendonly yes

镜像更新

docker 中如果想更新对应的镜像,是无法直接修改的,需要先启动对应的容器。

进入容器后进行对应的修改,例如更新第三方库等等,完成修改后退出容器,通过docker ps查看当前容器的 container-id

然后使用下面命令,将该容器的改动提交到一个新的镜像中(原本的镜像没有被修改,本质是创建了一个新的镜像,并保留了对应的更改)

1
docker commit [container-id] [new-image-name]

可以使用docker images查看当前的镜像文件,并使用docker run启动新镜像。

镜像导出与导入

如果想要将镜像快速分享给别人,可以使用以下命令导出对应的镜像,打包为 tar 文件。

1
docker save -o [output-file.tar] [image-id_or_image-name]

例如

1
docker save -o myimage.tar myimage

通过使用docker load即可导入对应的 tar 文件。

1
2
3
docker load -i [input-file.tar]
或者
docker load < [input-file.tar]

DockerFile

dockerfile 常见语法

如果需要构造复杂一些的 docker 镜像,就需要使用 dockerfile,常用来搭建 Web 服务,可以将本地的代码挂载到容器中,并快速配置所需环境,非常的方便。

如果只需要一个文件,就可以拉取、配置依赖、挂载数据、启动项目。

例如下面的 dockerfile,就可以拉取 python3 镜像,并将 app 目录复制到容器中,并安装依赖、配置端口,最后使用命令启动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 使用官方的 Python 3 基础镜像
FROM python:3

# 将当前目录下的文件复制到镜像中的 /app 目录
COPY . /app

# 设置工作目录
WORKDIR /app

# 安装依赖包
RUN pip install -r requirements.txt

# 暴露容器监听的端口
EXPOSE 80

# 定义容器启动时运行的命令
CMD ["python", "app.py"]

常见的指令如下:

Dockerfile 指令 说明
FROM 指定基础镜像,用于后续的指令构建。
MAINTAINER 指定Dockerfile的作者/维护者。
LABEL 添加镜像的元数据,使用键值对的形式。
RUN 在构建过程中在镜像中执行命令。
CMD 指定容器创建时的默认命令。(可以被覆盖)
ENTRYPOINT 设置容器创建时的主要命令。(不可被覆盖)
EXPOSE 声明容器运行时监听的特定网络端口。
ENV 在容器内部设置环境变量。
ADD 将文件、目录或远程URL复制到镜像中。
COPY 将文件或目录复制到镜像中。
VOLUME 为容器创建挂载点或声明卷。
WORKDIR 设置后续指令的工作目录。
USER 指定后续指令的用户上下文。
ARG 定义在构建过程中传递给构建器的变量,可使用 “docker build” 命令设置。
ONBUILD 当该镜像被用作另一个构建过程的基础时,添加触发器。
STOPSIGNAL 设置发送给容器以退出的系统调用信号。
HEALTHCHECK 定义周期性检查容器健康状态的命令。
SHELL 覆盖Docker中默认的shell,用于RUN、CMD和ENTRYPOINT指令。

FROM

FROM 是 dockerfile 中的第一个指令,指定一个基础的镜像,可以理解为代替docker pull。格式如下:

1
FROM <image>[:<tag>]

RUN

RUN 指令可以在容器中执行命令,以构建所需的依赖和配置文件等。

RUN 指令在 dockerfile 中可以出现多次,并且每个 RUN 指令都会创建一个新的镜像层。为了减少镜像的层数,可以将多个命令合并为一行,例如使用&&连接多个命令,这样在构建镜像时只会生成一个新的层。

1
RUN apt-get update && apt-get install -y python3

也可以使用数组的格式,防止出现一些 shell 的解释错误问题。

1
2
RUN ["apt-get", "update"]
RUN ["apt-get", "install", "-y", "python3"]

CMD

CMD 在 dockerfile 中只能包含一个,可以理解为项目的启动命令。

1
CMD python app.py

同样可以使用数组

1
CMD ["python", "app.py"]

ENTRYPOINT

ENTRYPOINT 用于配置容器启动时的默认执行命令。它类似于 CMD 指令,但有一些关键的区别。ENTRYPOINT 指令的格式与 CMD 指令类似,可以使用 Shell 格式或数组格式,但在使用时,需要注意以下几点:

  1. ENTRYPOINT 指令的命令会在容器启动时始终执行,无论在 docker run 命令中是否指定了其他命令。它不会被覆盖,而是作为容器的主要执行命令。
  2. 如果在 docker run 命令中指定了其他命令,这些命令将作为 ENTRYPOINT 指令的参数进行传递。也就是说,ENTRYPOINT 指令中的命令将成为执行时的前缀。

EXPOSE

EXPOSE 用于声明容器在运行时监听的网络端口,它只是做一个端口的说明,并不会进行端口映射。如果需要端口映射,需要使用-p参数。

1
EXPOSE 80

ENV

ENV 指令用于设置环境变量,它允许在镜像构建过程中设置环境变量,这些环境变量将在容器运行时可用。ENV 指令的格式是 ENV key=value,其中 key 是环境变量的名称,value 是环境变量的值。如果在 docker run 中使用了-e也配置了环境变量,那么 ENV 的会被覆盖。

1
2
ENV MY_NAME John Doe
ENV APP_HOME /app

ADD

ADD 用于将文件、目录或远程 URL 复制到镜像中。它类似于 COPY 指令,但在功能上更强大。

ADD 指令的格式是 ADD source destination,其中 source 是要复制的源文件、目录或 URL,destination 是复制后的目标路径。destination 可以是绝对路径或相对于工作目录的路径。除了复制文件和目录,ADD指令还支持自动解压缩。如果 source 是一个压缩文件(例如.tar、.tar.gz、.tgz、.zip等),那么 ADD 指令会自动解压缩该文件到 destination 指定的目录。以下是一些 ADD 指令的示例:复制本地文件到镜像中:

1
2
3
4
5
6
7
ADD app.py /app/

ADD src/ /app/

ADD https://example.com/file.tar.gz /tmp/

ADD app.tar.gz /app/

COPY

COPY 指令就是将文件复制到容器中,不支持解压缩。

1
2
3
COPY app.py /app/

COPY src/ /app/

VOLUME

VOLUME 用于配置数据的挂载,用于配置文件共享,尤其是加载大量文件的时候,使用挂载可以方便文件储存。一个 dockerfile 中可以配置多个 VOLUME 声明。由于其可移植性,因此不可以直接指定宿主机挂载目录,该命令只是创建一个容器挂载目录。

1
VOLUME ["/app/data", "/app/logs"]

然后通过 docker run 命令将宿主机目录挂载进去。

1
$ docker run -v /host/data:/app/data -v /host/logs:/app/logs my_image

WORKDIR

WORKDIR 用于设置工作目录,也称为当前工作目录。在容器启动时,进程的当前工作目录将被设置为 WORKDIR 指令所指定的目录。我们使用 WORKDIR 指令将工作目录设置为/app。设置工作目录后,再搭配 CMD 命令就不需要指定绝对路径了。

1
2
3
4
5
# 设置工作目录
WORKDIR /app

# 容器启动时运行的命令
CMD ["python", "app.py"]

USER

USER 用于指定在容器中运行镜像时要使用的非特权用户。默认情况下,docker 容器在启动时以 root 用户身份运行,这意味着容器内的进程具有最高权限。然而,为了加强安全性,避免潜在的安全风险,最好以非特权用户的身份运行容器中的应用程序。

使用 useradd 命令创建了一个名为 myuser 的新用户,并使用-ms /bin/bash选项指定了创建用户时使用的 shell。然后,通过 USER 指令切换到了 myuser 用户。这样,在容器运行时,进程将以 myuser 用户的身份运行,而不是以 root 用户身份。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
FROM ubuntu:latest

# 创建一个新用户并切换到该用户
RUN useradd -ms /bin/bash myuser
USER myuser

# 设置工作目录
WORKDIR /app

# 复制应用程序到工作目录
COPY . .

# 设置环境变量
ENV APP_ENV production

# 容器启动时运行的命令
CMD ["python", "app.py"]

ARG

ARG 是用于定义构建参数(build-time arguments)。构建参数允许在构建镜像时传递变量值,这些变量可以在 dockerfile 中使用,并且在构建过程中可以通过--build-arg选项进行覆盖。

1
2
3
4
5
6
7
FROM ubuntu:latest

# 定义构建参数
ARG MY_ENV=production

# 使用构建参数设置环境变量
ENV ENVIRONMENT=$MY_ENV

相当于一开始设置了 MY_ENV 的参数,但也可以使用--build-arg覆盖。

1
$ docker build --build-arg MY_ENV=development -t my_image .

SHELL

SHELL 指令可以指定在容器中使用的 shell 解释器。

1
2
3
4
5
# 设置使用bash作为Shell解释器
SHELL ["/bin/bash", "-c"]

# 在构建镜像时执行命令
RUN echo "Hello, Docker!"

Dockerfile 定制镜像

docker build

docker build 命令用于快速使用 dockerfile 构建镜像。常用命令如下:

使用-f命令可以指定 dockerfile 文件。

1
docker build -f /path/to/a/Dockerfile .

也可以不指定,则默认加载当前目录下的 dockerfile

1
docker build .

使用-t参数可以指定镜像的名称和 tag。

1
docker build -t ubuntu-nginx:v1 . 

其中 tag 是可以省略的。通常情况下,会省略-f默认加载当前目录下的 dockerfile。

1
docker build -t my_image .

定制镜像

此部分将搭建一个基于 python3.9 的镜像,并把本地的工作目录复制到镜像中,并启动项目。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 使用官方Python镜像作为基础镜像
FROM python:3.9-slim

# 设置工作目录
WORKDIR /app

# 将当前目录内容复制到位于 /app 的容器中
COPY . /app

# 安装 requirements.txt 中指定的任何所需包
RUN apt-get update && pip install --no-cache-dir -r requirements.txt

# 暴露端口
EXPOSE 5000

# 在容器启动时运行 app.py
CMD ["python", "app.py"]

使用 docker build 构建镜像。

1
docker build -t my_web .
1
docker run --name flask-app -p 5000:5000 -d my_web

也可以使用挂载的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 使用官方Python镜像作为基础镜像
FROM python:3.9-slim

# 设置工作目录
WORKDIR /app

# 将 requirements.txt 复制到容器内
COPY requirements.txt /tmp/requirements.txt

# 安装 requirements.txt 中指定的任何所需包
RUN apt-get update && pip install --no-cache-dir -r /tmp/requirements.txt

# 暴露端口
EXPOSE 5000

# 在容器启动时运行 app.py
CMD ["python", "app.py"]

1
docker build -t my_web .

docker run 的时候使用-v添加挂载目录。

1
docker run --name flask-app -p 5000:5000 -v ./app:/app -d my_web

多阶段构建

多阶段构建是一种有效的优化技术,可以在一个 dockerfile 中使用多个 FROM 指令,每个 FROM 指令都代表一个构建阶段。每个构建阶段都可以从之前的阶段复制所需的文件,并执行特定的构建操作。使用多阶段构建可以使得最终生成的镜像只包含运行应用程序所必需的文件和依赖,而不包含构建过程中产生的不必要文件和依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 构建阶段1
FROM golang:1.17 AS builder

WORKDIR /app
COPY . .

# 编译应用程序
RUN go build -o myapp

# 构建阶段2
FROM alpine:latest

# 复制编译后的应用程序
COPY --from=builder /app/myapp /usr/local/bin/

# 设置工作目录
WORKDIR /usr/local/bin

# 容器启动时运行的命令
CMD ["myapp"]

阶段一

  • FROM golang:1.17 AS builder:这行指定了基础镜像golang:1.17,并且给这个阶段命名为builder
  • WORKDIR /app:设置工作目录为/app
  • COPY . .:将当前目录(.)的所有内容复制到容器的工作目录(/app)。
  • RUN go build -o myapp:运行go build命令来编译应用程序,并将编译后的可执行文件命名为myapp

阶段二

  • FROM alpine:latest:这行指定了基础镜像alpine:latest
  • COPY --from=builder /app/myapp /usr/local/bin/:这行将第一阶段编译好的myapp可执行文件从builder阶段的镜像复制到当前阶段的/usr/local/bin/目录。
  • WORKDIR /usr/local/bin:设置工作目录为/usr/local/bin,这是myapp可执行文件的所在目录。
  • CMD ["myapp"]:这行指定了容器启动时运行的命令,即运行myapp可执行文件。

通过这种方式,最终的镜像只包含了运行Go应用程序所必需的文件,而不包含编译应用程序所需的工具和依赖。这样可以大大减少镜像的大小,提高部署的效率。

Docker compose

docker compose 是什么

前面了解了 dockerfile 在搭建环境的时候非常方便,但如果需要快速搭建一个项目,其中包含了 python、mysql、redis、nginx 等众多环境,每次启动都需要使用 docker run 并加上一堆参数分别启动每个镜像,还需要指定连接来链接各个容器。

因此 docker compose 可以理解为构建、定义项目的编排关系,只需要将固定的内容、挂载卷等等写入 docker compose。

启动时只需要 docker compose up 就行,停止也只需要 docker compose stop/down。

1
2
3
4
5
6
7
8
# 启动
docker compose up -d

# 关闭
docker compose down

# 停止
docker compose stop

所以 dockerfile 是描述单个镜像的构建过程,而 docker compose 是整个项目的构建过程,可以快速搭建一个项目所需要的环境。

如何编写 docker compose

一般来说,我们会将其文件命名为docker-compose.yml,采用 YAML 的格式进行编写。

标准模板文件应该包含 version、services、networks 三大部分,最关键的是 services 和 networks 两个部分。

version

这里的 version 有着与 docker compose 之间的对应关系,如下。

Docker Compose 版本 version 字段
1.0.x ‘1’
1.1.x ‘2’
1.2.x ‘2.1’
1.3.x ‘2.1’
1.4.x ‘2.1’
1.5.x ‘2.1’
1.6.x ‘2.1’
1.7.x ‘2.1’
1.8.x - 1.10.x ‘2.1’
1.11.x ‘2.1’ 或 ‘2.2’(取决于特性使用)
1.12.x - 1.13.x ‘2.1’ 或 ‘2.2’(取决于特性使用)
1.14.x ‘2.1’ 或 ‘2.3’(取决于特性使用)
1.15.x ‘2.1’ 或 ‘2.3’(取决于特性使用)
1.16.x - 1.17.x ‘2.1’ 或 ‘2.3’(取决于特性使用)
1.18.x ‘2.1’ 或 ‘2.4’(取决于特性使用)
1.19.x ‘3.0’
1.20.x ‘3.0’
1.21.x ‘3.0’
1.22.x ‘3.0’
1.23.x ‘3.0’
1.24.x - 1.27.x ‘3.0’
1.28.x ‘3.7’ 或 ‘3.8’(取决于特性使用)
1.29.x ‘3.7’ 或 ‘3.8’(取决于特性使用)

本地执行下面命令可以查看 docker compose 的版本

1
docker compose version

然后找上表填入对应的版本。

1
version: '3.0'

services

在 docker-compose 中需要指定启动的服务,以及对应内容。

1
2
version: '3.0'
services:

每个服务必须指定是直接拉取镜像(配置 image),还是编译写好的 dockerfile(build)。

1
2
3
4
5
6
7
8
version: '3.0'
services:
web:
build: .
ports:
- "5000:5000"
mysql:
image: mysql:5.7
build

build 将指定一个 dockerfile 作为创建的命令,其中有两种方式可以进行指定。

第一种是配置目录,其中 webapp 代表一个相对路径,该目录下包含一个 Dockerfile 文件。

1
2
3
services:
web:
build: ./webapp

第二种是配置对应 dockerfile 文件位置。通过context指定对应目录,以及dockerfile指定对应的 dockerfile 文件,这种可以解决文件名不为Dockerfile的情况。

1
2
3
4
5
services:
web:
build:
context: ./webapp
dockerfile: Dockerfile_webapp
ports

映射端口,前面是宿主机端口,后面是容器内的端口。

1
2
3
4
5
6
7
services:
web:
build:
context: ./webapp
dockerfile: Dockerfile_webapp
ports:
- "5000:5000"
volumes

挂载卷配置,例如 mysql 在启动时,将本地的数据卷进行挂载,可以设置多个挂载卷。

1
2
3
4
5
6
7
8
9
10
11
12
13
services:
web:
build:
context: ./webapp
dockerfile: Dockerfile_webapp
ports:
- "5000:5000"

mysql:
image: mysql:5.7
volumes:
- ./mysql-volume/mysql-data:/var/lib/mysql
- ./mysql-volume/mysql-log:/var/lib/mysql/mysql-log
environment

很多时候需要配置环境变量,例如 mysql 的账号密码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
services:
web:
build:
context: ./webapp
dockerfile: Dockerfile_webapp
environment:
FLASK_ENV: development
ports:
- "5000:5000"

mysql:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_USER: user
MYSQL_PASSWORD: password
volumes:
- ./mysql-volume/mysql-data:/var/lib/mysql
- ./mysql-volume/mysql-log:/var/lib/mysql/mysql-log
depends_on

用于解决容器依赖问题,处理启动先后的情况。可以先启动 mysql 再启动 web 服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
services:
web:
build:
context: ./webapp
dockerfile: Dockerfile_webapp
environment:
FLASK_ENV: development
ports:
- "5000:5000"
depends_on:
- mysql

mysql:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_USER: user
MYSQL_PASSWORD: password
volumes:
- ./mysql-volume/mysql-data:/var/lib/mysql
- ./mysql-volume/mysql-log:/var/lib/mysql/mysql-log

networks

默认网络

当执行这个 docker compose 文件的时候,会自动创建一个容器网络。web 和 mysql 都会添加到这个网络中,并且名称也为 web 和 mysql。其中在容器网络中,它们是可以互相通信的,且容器间的通信端口为容器端口。如果没有特定需求,不需要配置相关策略,将使用默认网络(_default)进行通信。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
services:
web:
build:
context: ./webapp
dockerfile: Dockerfile_webapp
environment:
FLASK_ENV: development
ports:
- "5000:5000"
depends_on:
- mysql

mysql:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_USER: user
MYSQL_PASSWORD: password
volumes:
- ./mysql-volume/mysql-data:/var/lib/mysql
- ./mysql-volume/mysql-log:/var/lib/mysql/mysql-log

案例

flask + mysql

结构

1
2
3
4
5
6
7
docker-compose.yaml
Dockerfile
requirements.txt
mysql-volume
app
|run.py
|app

Dockerfile

首先编写一个 flask 的 Dockerfile 文件。

1.使用 python 3.9 版本

2.将 /app 设置为工作目录

3.将当前目录下的 requirements.txt 复制到 /tmp/requirements.txt

4.使用命令更新并安装依赖库

5.使用 python run.py 启动项目。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 使用官方Python镜像作为基础镜像
FROM python:3.9-slim

# 设置工作目录
WORKDIR /app

# 将 requirements.txt 复制到容器内
COPY requirements.txt /tmp/requirements.txt

# 安装 requirements.txt 中指定的任何所需包
RUN apt-get update && pip install --no-cache-dir -r /tmp/requirements.txt

# 在容器启动时运行 app.py
CMD ["python", "run.py"]

docker-compose.yaml

1.配置一个 web 服务,使用当前目录下(.)的 Dockerfile 文件进行构建。

2.配置挂载目录、映射端口到 5000 端口。

3.配置启动项,先启动 mysql,再启动 web 服务。

4.mysql 使用官方 mysql:5.7 镜像。

5.配置 mysql 环境变量,配置账号密码。

6.配置 mysql 数据挂载卷。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
version: '3.0'
services:
web:
build: .
environment:
FLASK_ENV: development
volumes:
- ./app:/app
ports:
- "5000:5000"
depends_on:
- mysql

mysql:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_USER: user
MYSQL_PASSWORD: password
volumes:
- ./mysql-volume/mysql-data:/var/lib/mysql

但是需要注意,如果使用数据库,容器直接是通过容器网络进行连接的,因此 HOSTNAME 需要设置成 docker-compose.yml 中 mysql 的名称,即(mysql),而不是 127.0.0.1

1
2
3
4
5
6
7
8
class BaseConfig(object):
SECRET_KEY = 'xxx'
HOSTNAME = 'mysql'
PORT = '3306'
USERNAME = 'root'
PASSWORD = 'rootpassword'
DATABASE = 'web'
SQLALCHEMY_DATABASE_URI = f"mysql+pymysql://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}?charset=utf8"