面向数据科学家的Docker指南
date
Dec 24, 2023
slug
docker-guide-for-data-scientists
status
Published
tags
Compute Science
summary
与运维无关,只是一份简单到刚好满足数据科学家需求的Docker教程
type
Post
Docker是什么?解决了什么问题?一些需要拎清的概念简单创建一个Python容器并发布该镜像通过docker-compose安装容器(以MySql为例)补充打包Python项目为镜像补充一些技巧一些常用的docker命令总结参考
Docker是什么?解决了什么问题?
Docker是Linux容器的一种封装,它可以将编写的Python应用程序及其环境依赖统统打包在一个文件里面。运行这个文件,就会生成一个虚拟容器。程序在这个虚拟容器里运行,就好像在真实的物理机上运行一样。有了 Docker,就不用担心任何环境问题。
举一个最常见的例子吧,这几年国内的数据科学竞赛如雨后春笋,这些竞赛由于没有像Kaggle一样的平台,所以一般都是要求参赛选手在本地建模,然后提交测试结果到在线系统进行评分。为了防止参赛选手作弊,出题方一般会要求排名靠前的选手提交源代码来进行复现结果。但是,试想一下,每个选手所写代码的平台首先可能不一样,如Ubuntu, Windows等,再者各选手用的包也会不一样,以及各种包的版本也会现差异,这时候,做为出题方单单想要复现个结果就很费时费力了。这时候就能体现docker的魔力了,只需要要求参赛选手提交一个Docker镜像即可直接复现,完全不用在乎选手是在什么平台下用了什么版本的什么包了,这就是docker解决环境问题的典型案例。在这个案例中,Docker充当了提供环境的功能,除此之外,Docker还有像组件微服务架构这样的作用。
对于Docker的安装,官方文档给了详细的教程。Windows平台个人推荐直接安装Docker Desktop,然后优先选择WSL 2作为后端,这样在wsl和win中便可以无缝使用docker了。
本文的脉络如下:动手前,先介绍几个关键的概念;然后通过一个简单的动手案例来理解镜像、容器以及随之的一些命令;之后便是两个最常用的应用场景,通过这两个常见的应用场景,学会docker-compose和Dockerfile的写法,以掌握这两种用法。
一些需要拎清的概念
- 镜像:如果装过操作系统,就一定对这个词语不陌生,毕竟装系统一般都需要下载ISO或者IMG镜像文件,此处的镜像和操作系统的镜像文件的概念是一样的,就是指包含了所有运行所需的文件的而形成的一个大的文件。注意,镜像是一个“死”的东西,是一个文件。
- 容器:容器就是对应镜像运行起来之后的程序了。注意,容器是“活”的,是个程序,因此容器有多种状态,比如运行中(Running)、已退出(Exited)、暂停中(Paused)等。
对容器和镜像做以下几点说明:
- 每个容器都是从一个Docker镜像创建的,但一个镜像可以生成多个容器,就像一个windows镜像可以用来给多台主机装系统一样;
- 镜像文件是通用的,一台机器的镜像文件拷贝到另一台机器,照样可以使用,且结果一模一样。为了方便镜像的共享,一般将制作好的镜像文件上传到网上的镜像仓库,如Docker Hub,就像把写好的代码开源到GitHub上供别人使用一样。
容器本质上是进程,只不过做了隔离和资源限制,方便管理,对于容器内部的程序来说就好像是一个独立的操作系统。这就是为什么称Docker是Linux容器的一种封装。
既然镜像是个”文件“,那这个文件名的格式是怎样的呢?通常用
<image_name>:<tag>
来标识不同的镜像,image_name
即为镜像的名字,<tag>是镜像的标签
,名字好理解,那标签是干嘛用的呢?标签通常可以用来标识镜像的不同版本、配置或特性。因此,后续不管是下载 (docker pull xx:xx) 镜像还是运行(docker run xx:xx)镜像都要记住<image_name>:<tag>
这个格式。前面讲到,一个容器可以看做是做了隔离的独立的“操作系统”。那这个“操作系统”如何与外界来通信呢?这就有了Volume(卷)和Network(网络)这两个重要的概念:
- Volume(卷):
- Volume 是用来在宿主机和容器之间共享数据的机制。它允许容器持久化保存数据,并且在容器被删除后数据仍然保留。
- 通过 Volume,容器可以访问宿主机上的特定目录,也可以与其他容器共享数据。可以用于存储应用程序的数据、日志文件、配置文件等,实现数据的持久化和共享。
- Network(网络):
- Network 用于连接 Docker 容器,使得它们可以相互通信,也可以与外部网络进行通信。通过网络,容器可以相互通信,实现微服务架构或多个容器协同工作的应用场景。
- Docker 支持多种网络模式,包括桥接网络、主机网络、覆盖网络等,每种网络模式都有不同的用途和特点。具体如下:
- 桥接网络(Bridge Network):这是 Docker 默认的网络模式,它会为每个容器分配一个独立的 IP 地址,并且容器之间可以相互通信。桥接网络也允许容器与宿主机进行通信,但默认情况下不允许外部网络访问容器。
- 主机网络(Host Network):使用主机网络模式时,容器将直接使用宿主机的网络栈,即容器和宿主机共享同一个网络命名空间。这意味着容器可以直接使用宿主机的 IP 地址和端口,从而提供更高的网络性能。
- 覆盖网络(Overlay Network):覆盖网络允许多个 Docker 守护进程之间的容器进行通信,通常用在跨多个主机的容器集群上。
- 无网络(None Network):在无网络模式下,容器不会连接到任何网络,此时,容器内部的进程只能通过 Unix socket 进行通信,而无法通过http/ip网络进行通信。
一句话概括,就是:Volume(卷)用于在宿主机和容器之间共享数据,Network(网络)用于连接 Docker 容器。
简单创建一个Python容器并发布该镜像
本小节先以python镜像为例,先拉取一个官方镜像,然后运行该镜像为容器之后顺便好配置环境,再将该容器打包为一个新的属于自己的镜像,最后将打包好的镜像推送到docker hub上。有什么用呢?维护一个自己的镜像,可以除去反复配环境的诅咒!
首先去Docker官方维护的Python镜像页面,找自己想要的Python镜像,如果不知道选什么,就直接选
python:<version>
吧,以3.8为例执行docker pull python:3.8
拉取该镜像,大概900M左右的大小。docker pull python:3.8
然后运行
docker images
便可以查看到这个镜像,如下:REPOSITORY TAG IMAGE ID CREATED SIZE python 3.8 415775fb4b1f 2 months ago 998MB
执行
docker run -itd --name py38 -v ~/dataset:/data python:3.8 bash
以运行该镜像。不出意外,此时该容器已在后台运行了,可以通过docker ps
来查看正在运行的容器:CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 06a9615824cd python:3.8 "bash" 10 minutes ago Up 10 minutes py38
对于上述docker run命令,有如下解释:
-itd
:分配一个伪终端(pseudo-TTY),并以后台模式(detached mode)运行容器,即在后台运行容器,并保持标准输入打开。
--name py38
:给容器指定一个名称py38
,以便后续可以通过名称来引用这个容器。
-v ~/dataset:/data
:将宿主机中的~/dataset
目录挂载到容器内的/data
目录,实现宿主机和容器之间的数据共享。
python:3.8
:之前拉取镜像的标识,标签一定不能少!不然会再去拉取默认镜像。
bash
:作为容器的启动命令,这将会启动一个交互式的 Bash shell,允许用户在容器内部进行交互操作。
docker run的参数巨多,不要想着全记下来,用到的时候去查就好了,经常用的也不过是设置Volumn,设置端口映射,设置容器名称,设置容器网络这些。
此时,docker已经在后台运行了,如果想要进入docker的命令行界面,可以执行
docker exec -it py38 /bin/bash
,之后,便身处这个”操作系统“里面了。对docker exec有如下解释:docker exec
: 这是Docker命令,用于在运行中的容器中执行命令。
-it
: 这是两个选项的结合,i
表示交互模式(stdin开启),t
表示分配一个伪终端。
py38
: 表示要执行命令的目标容器的名称或ID,此处的容器名称为py38
/bin/bash
: 这是要在容器中执行的命令,这里是启动一个bash shell,以便与容器进行交互。
进入容器内的终端之后,可以安装git, conda 等常用的环境。以conda为例,可以通过如下脚本快速安装:
mkdir -p ~/miniconda3 wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda3/miniconda.sh bash ~/miniconda3/miniconda.sh -b -u -p ~/miniconda3 rm -rf ~/miniconda3/miniconda.sh ~/miniconda3/bin/conda init bash
然后还可以创建环境以及自定义的一些配置了。在配置完成自己的环境之后,现在把上述打包为一个镜像供以后直接使用。
执行命令
docker commit -a "bairch" -m "my py env" 06a9615824cd mypy38:v1
将该容器保存为镜像,当看到生成sha256值时表示已打包完成。brc@DESKTOP-72HHG9G:~$ docker commit -a "bairch" -m "my py env" 06a9615824cd mypy38:v1 sha256:c2a3088898089f05803ef3dc5630a8c5bb17bdbcd43090bc753bb325fa2b01c5
此时,可以通过
docker images
来查看当前已有的镜像,如下,电脑中除了原先从docker官网pull到的镜像之外,还有了一个新的名为mypy38标签为v1的镜像,即为刚才创建的镜像。brc@DESKTOP-72HHG9G:~$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE mypy38 v1 c2a308889808 About a minute ago 1.84GB python 3.8 415775fb4b1f 2 months ago 998MB
对刚刚创建镜像的docker commit命令的各部分有如下详细解释:
-a "bairch"
:使用a
参数指定了作者信息为 "bairch"
-m "my py env"
:使用m
参数指定了提交时的说明信息为 "my py env"
06a9615824cd
:这是要保存为镜像的容器的 ID
mypy38:v1
:这是新镜像的名称和标签。新镜像的名称为mypy38
,标签为v1
创建完镜像之后,便可以试一下,执行
docker run -itd mypy38:v1
在后台运行刚刚创建的镜像文件。然后通过docker ps
来查看正在运行的容器中已经有了刚刚启动的容器,此时注意该容器的NAMES为dreamy_stonebraker,这是因为我在docker run的时候没有通过--name <container name>
为该容器指定一个名称,因而显示了默认名称。brc@DESKTOP-72HHG9G:~$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 28051d25b4ee mypy38:v1 "bash" 5 minutes ago Up 5 minutes dreamy_stonebraker
通过
docker exec -it 28051d25b4ee bash
进入到该容器的命令行,就可以看到刚刚安装好的各种环境配置。接下来可以尝试把这个创建的镜像推送到docker hub,供别人使用。
既然要推送到docker hub,自然要有账户,先去官网注册一个账号,然后在本地 通过docker login 登录,同时,可以在docker hub创建一个空白仓库。
接着使用
docker tag mypy38:v1 bairch/mypy38:v1
命令给本地镜像打上 Docker Hub 用户名和标签。该命令的组成为docker tag <local_image id>:<tag> <hub-username>/<repo-name>:<tag>
然后docker push 直接提交
brc@DESKTOP-72HHG9G:~$ docker push bairch/mypy38:v1 The push refers to repository [docker.io/bairch/mypy38] f32c3e8a81ae: Pushed 02d14c5fe2c0: Mounted from library/python f1c817089aa8: Mounted from library/python aa19347b89d4: Mounted from library/python a0814d1f5387: Mounted from library/python ac7146fb6cf5: Mounted from library/python 209de9f22f2f: Mounted from library/python 777ac9f3cbb2: Pushed ae134c61b154: Mounted from library/python v1: digest: sha256:39b0bc01e69eed1023dd4fb89e4463f504596292ca51bcf2c72edd2633dbdc32 size: 2220
此时可以打开docker-hub 查看刚刚已推送的镜像

通过docker-compose安装容器(以MySql为例)
在Docker中通过docker-compose来拉取镜像并运行一个容器,这应该是最常用的场景了,除了工作之外,还有很多好玩的镜像(比如Alist, jellyfin, nextcloud等)都可以采样该方式来安装。

Docker Compose是通过YAML配置文件来定义和运行多容器 Docker 应用程序的工具。也就是说,通过一份配置文件来定义和管理多个相关联的容器容器。
Docker-Compose将所管理的容器分为三层,分别是工程(project),服务(service)以及容器(container)。Docker-Compose运行目录下的所有文件(docker-compose.yml,extends文件或环境变量文件等)组成一个工程,若无特殊指定工程名即为当前目录名。一个工程当中可包含多个服务,每个服务中定义了容器运行的镜像,参数,依赖。Docker-Compose 通过一个配置文件来管理多个Docker容器,在配置文件 (docker-compose.yml) 中,所有的容器通过services来定义,然后使用docker-compose脚本来启动、停止和重启应用和应用中的服务以及所有依赖服务的容器。
首先创建一个项目文件夹
proj_mysql
,然后按照如下格式创建各子文件夹mysql_serve ├── conf │ └── my.cnf # MySQL配置文件 ├── db # 数据库数据文件目录 ├── docker-compose.yml # docker-compose.yml文件 └── logs # 日志存放目录
接下来编辑项目目录中的docker-compose.yml文件为如下:
version: '3' # 表示使用的是 Docker Compose 文件的版本. 这里使用的是版本3的语法,它定义了Docker Compose 文件的结构和支持的功能。 services: # 这是一个关键字,用于定义服务的部分。在这个部分下,可以列出所有的服务以及它们的配置。本配置下只有mysql, 但是如果同时还有别的服务需求,都可以放到servers下。 mysql: # 一个服务的名称,也可以叫阿猫阿狗 restart: always # 表示在容器退出时,Docker 会自动重启这个容器 privileged: true # 标识容器拥有特权模式 image: mysql:8.0.32 # 表示使用的 MySQL 镜像的名称和标签 container_name: mysqlServer33106 # 为该容器指定了一个名称 volumes: # 定义容器和宿主机之间的数据卷映射关系;格式为<宿主机地址>:<容器内地址>,其中宿主机地址是相对项目目录路径,容器内地址是绝对路径 - ./db:/var/lib/mysql # 将容器内的地址映射到宿主机上,这样当容器退出了,数据库db文件还在 - ./conf:/etc/mysql/conf.d # 同上,不会因为容器挂掉,mysql配置文件就丢失 - ./logs:/logs # 同上,可以查看容器日志 command: # 定义了在容器启动时要执行的命令。此处定义了MySQL服务的一些配置参数 --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci --explicit_defaults_for_timestamp=true environment: # 定义了容器的环境变量,此处包括了数据库的root用户密码、用户、密码等信息。 MYSQL_ROOT_PASSWORD: "Abc1234Q" MYSQL_USER: "user1" MYSQL_PASSWORD: "Abc1234Q" MYSQL_INITDB_SKIP_TZINFO: "Asia/Shanghai" ports: # 定义了容器的端口映射关系,此处将主机的33106端口映射到容器内的3306端口 - 33106:3306 # <宿主机端口>:<容器端口> network_mode: "bridge" # 定义了容器的网络模式为桥接模式
通过上述配置文件的注释大概就知道这份配置文件主要在配置啥了,不外乎是端口映射、卷映射、环境变量、容器网络、镜像源等。
接下来就是添加mysql的配置文件了,(没有特殊需求可以不用自定义配置,直接使用默认即可)编辑 my.cnf 文件如下:
###### [mysql]配置模块 ###### [mysql] # 设置MySQL客户端默认字符集 default-character-set=utf8mb4 socket=/var/lib/mysql/mysql.sock ###### [mysqld]配置模块 ###### [mysqld] port=3306 user=mysql sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION datadir=/var/lib/mysql socket=/var/lib/mysql/mysql.sock # MySQL8 的密码认证插件 default_authentication_plugin=mysql_native_password # 禁用符号链接以防止各种安全风险 symbolic-links=0 # 允许最大连接数 max_connections=1000 # 服务端使用的字符集默认为8比特编码的latin1字符集 character-set-server=utf8mb4 # 创建新表时将使用的默认存储引擎 default-storage-engine=INNODB # 表名存储在磁盘是小写的,但是比较的时候是不区分大小写 lower_case_table_names=0 max_allowed_packet=16M # 设置时区 default-time_zone='+8:00' # binlog 配置 log-bin = /logs/mysql-bin.log expire-logs-days = 90 max-binlog-size = 500M # server-id 配置 server-id = 1 ###### [client]配置模块 ###### [client] default-character-set=utf8mb4
接下来就可以直接启动了,执行
docker-compose up -d
将会在后台启动该服务。通过docker-compose ps
来查看启动的容器。至此,一个MySQL服务其实已经建好了,可以通过SQL客户端尝试连接一下数据库了。
docker-compose ps
用于查看docker-compose管理的服务的状态,而docker ps
用于查看主机上所有正在运行的Docker容器的状态。需要注意的是,MySQL服务器默认是没有远程访问权限的,需要手动给MySQL用户配置远程访问权限。具体操作如下:
# 进入正在运行的MySQL容器内部 docker exec -it <mysql_container_name> /bin/bash # 使用root权限登录到MySQL服务器 mysql -u root -p # 授予远程访问权限给特定的用户。其中的%表示允许任何地址连接 alter user '<user name>'@'%' identified with mysql_native_password by '<login password>';
若想要停止该服务,进入该项目目录执行
docker-compose down
即可,该命令会停止并移除通过docker-compose.yml启动的所有容器,同时还会移除相关的网络和卷,即可以直接停止整个应用。补充
其实MySQL的这个例子并不好,毕竟里面只有一个服务,没有发挥docker-compose的真正威力(docker-compose是可以同时管理多个服务的)。
下面以gitea为例,来看看gitea的docker-compose配置文件:
version: "3" networks: gitea: external: false services: server: image: gitea/gitea:latest container_name: gitea environment: - USER_UID=131 - USER_GID=138 - GITEA__database__DB_TYPE=mysql - GITEA__database__HOST=db:3306 - GITEA__database__NAME=gitea - GITEA__database__USER=gitea - GITEA__database__PASSWD=gitea restart: always networks: - gitea volumes: - ./gitea:/data - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro ports: - "3000:3000" - "222:22" depends_on: - db db: image: mysql:8 restart: always environment: - MYSQL_ROOT_PASSWORD=gitea - MYSQL_USER=gitea - MYSQL_PASSWORD=gitea - MYSQL_DATABASE=gitea networks: - gitea volumes: - ./mysql:/var/lib/mysql
在上述配置文件中,services下存在两个服务,分别的server和db,这两个服务即为两个容器,每个容器内的配置其实和上面的MySQL配置差不多,需要注意的是:
- 通过depends_on来定义了服务之间的依赖关系,这样在
docker-compose up
的时候,会自动先启动被依赖的容器。当然,如果确实没有依赖关系也可以不用定义。
- 通过networks创建了一个名称为gitea的内部网络,供
server
和db
这两个服务在同一个网络中进行通信。external: false
表示这个网络是由docker-compose创建的内部网络,而不是外部已存在的网络。(设置这个就是方便且容易定制,不设置也没问题的,直接使用默认的bridge网络也可以。
打包Python项目为镜像
将已有的Python应用打包为一个镜像发布,这应该是用的次多的场景了。本小节将学习通过DockerFile来将已有的应用构建为镜像。
Dockerfile本质上是一个文本文件,其中明确定义了如何为我们的项目构建Docker镜像。Dockerfile里面一般包括如下几步:
- FROM: 所有Dockerfile的第一个指令都必须是
FROM
,用于指定一个构建镜像的基础源镜像,如果本地没有就会从公共库中拉取
- WORKDIR:指明应用在容器中的工作目录。
- COPY: 复制本机文件或目录到指定的容器目录,格式为 <src> <dest>
- RUN:用于运行安装任务从而向映像中添加额外的内容。比如说安装Python依赖包等。
- CMD:容器启动后运行的命令
假设现有如下应用:
fastapp ├── requirements.txt ├── Dockerfile └── app └── app.py └── <other .py files>
app.py如下:
from typing import Union from fastapi import FastAPI app = FastAPI() @app.get("/") def read_root(): return {"Hello": "World"} @app.get("/items/{item_id}") def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q}
现在按照上述对Dockerfile的介绍来编写fastapp目录下的Dockerfile文件,如下:
FROM python:3.9 WORKDIR /fastapp # 在容器内创建一个工作目录 COPY ./requirements.txt /fastapp/requirements.txt RUN pip install -r requirements.txt # 在容器的/app目录下安装依赖 COPY ./app /fastapp/app CMD ["uvicorn", "app.app:app", "--host", "0.0.0.0", "--port", "80"] # 在容器的/app目录下运行python main.py命令
注:
- Dockerfle中是无法写注释的,此处是为了方便
- 最后一行中的
"uvicorn", "app.app:app"
可能会有疑惑,哪来这么多app?第一个app,表示当前工作目录下的app目录,第二个app表示app.py文件;第三个app是指app.py中的对象app.
- 在构建镜像时,Docker创建了所谓的“层(layers)”。每一层都记录了Dockerfile中的命令所导致的更改,以及运行命令后镜像的状态。
- Docker在内部缓存这些层,这样在重新构建镜像时只需要重新创建已更改的层。例如,这里使用了
python:3.9
的基础镜像,相同容器的所有后续构建都可以重用它,因为它不会改变。需要注意的是,每当重新构建某一层时,Dockerfile
中紧随其后的所有层也都需要重新构建。例如,我们首先复制requirements.txt
文件,然后再复制应用程序的其余部分。这样之前安装的依赖项只要没有新的依赖关系,即使应用程序中的其他文件发生了更改,也不需要重新构建这一层。这一点在创建Dockerfiles
时一定要注意。因此,通过将pip
安装与应用程序其余部分的部署分离,可以优化容器的构建过程。
有了Dockerfile就可以执行
docker build -t docker-flask:0.1 .
来构建镜像了,具体进度如下:brc@DESKTOP-72HHG9G:~/project/fastapp$ docker build -t dfast-app:0.1 . [+] Building 14.4s (10/10) FINISHED docker:default => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 259B 0.0s => [internal] load metadata for docker.io/library/python:3.9 0.8s => [1/5] FROM docker.io/library/python:3.9@sha256:30678bb79d9eeaf98ec0ce83cdcd4d6f5301484ef86873a711e69df2ca77e8ac 0.0s => [internal] load build context 0.0s => => transferring context: 311B 0.0s => CACHED [2/5] WORKDIR /fastapp 0.0s => [3/5] COPY ./requirements.txt /fastapp/requirements.txt 0.1s => [4/5] RUN pip install -r requirements.txt 13.1s => [5/5] COPY ./app /fastapp/app 0.1s => exporting to image 0.2s => => exporting layers 0.2s => => writing image sha256:e4c65f0dac1caf5a8b4a82c5e84412c15dd03f54240ea15d5f69169c838ad3fe 0.0s => => naming to docker.io/library/dfast-app:0.1 0.0s What's Next? View a summary of image vulnerabilities and recommendations → docker scout quickview
上述命令中的
-t
的全称是--tag
,显然是给创建的镜像标识的。后面的.
表示用当前路径下的Dockerfile来创建镜像。有了镜像,就可以直接通过docker run创建并且启动容器了,如下:
brc@DESKTOP-72HHG9G:~/project/fastapp$ docker run --name myfastapp -p 800:80 dfast-app:0.1 INFO: Started server process [1] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://0.0.0.0:80 (Press CTRL+C to quit) INFO: 172.17.0.1:35006 - "GET /items/5?q=somequery HTTP/1.1" 200 OK INFO: 172.17.0.1:35006 - "GET /favicon.ico HTTP/1.1" 404 Not Found INFO: 172.17.0.1:35010 - "GET /items/5?q=som2 HTTP/1.1" 200 OK
上述命令,首先给这个容器命名为myfastapp, 然后将宿主机的端口800映射到了容器的80端口,80是因为在Dockerfile内设置好容器内的fastapi服务在80端口启动。
至此,已经完成了已有应用的镜像构建了。
补充
在开发应用程序的过程中,更重要的是要快速重新构建和测试,以检查验证过程中的每个中间步骤。为此,程序的开发人员需要依赖于fastapi等框架提供的自动重启功能(Debug模式下,修改代码自动重启)。而这一功能也可以在容器中使用。
为了启用自动重启,在启动Docker容器时将主机中的开发目录映射到容器中的app目录。这样fastapi就可以监听主机中的文件变化(通过映射)来发现代码更改,并在检测到更改时自动重启应用程序。
当然,这需要在构建镜像的时候,就在Dockerfile中写好fastapi以reload的模式(即debug模式)运行。如下:
CMD ["uvicorn", "app.app:app", "--host", "0.0.0.0", "--port", "80", "--reload"]
然后,docker run的时候,做好卷的映射,如下:
docker run --name myfastapp -v $PWD/app:/fastapp/app -p 800:80 dfast-app:0.1
通过把
$PWD/app
目录映射到容器的/fastapp/app
目录,这样容器内看到的目录app中的内容就是宿主机上的app目录中的内容,从而实现了,只要宿主机下代码变化,容器内的应用便自动重启。一些技巧
- 由于一些原因,国内访问官方镜像仓库dockerhub的速度捉襟见肘,因此一般会换源。以下是Ubuntu中Docker仓库换源方法:
打开/etc/default/docker文件(需要sudo权限),在文件的底部追加如下一行: DOCKER_OPTS="--registry-mirror=https://registry.docker-cn.com"
- docker需要管理员权限,要么每次都在所执行的命令前面加上
sudo
,要么可以将自己的linux用户名添加到docker组中
sudo usermod -aG docker $USER
一些常用的docker命令
# 查看所有的docker容器,不加参数-a时,仅查看当前正在运行的容器 docker ps -a # 查看所有的镜像 docker images # 删除某个镜像 docker rmi <image id> # 删除某个容器 docker rm <contaimer id> # 运行某个停止的容器 docker start <container id> # 停止某个容器 docker stop <container id> # 进入某个运行容器的命令行 docker exec -it <container id> /bin/bash
总结
本文首先介绍了Docker的几个重要概念,比如容器、镜像、Volumn等;然后以三个常用场景为例,了解了Docker的一些用法。最后,又写了几个技巧性的东西。后面再有的东西,就可以边google边使用了。