S8_Docker
Categories:
3 分钟阅读

使用 Docker 容器¶
接下来我们来尝试几个例子,体验 Docker 环境的独立性与易用性。
在 Ubuntu 容器中使用 shell¶
docker run -it --rm --name ubuntu-container ubuntu:20.04
这里,**--rm** 代表容器停止运行(退出)之后,会被立刻删除;--name 参数代表给容器命名,如果没有加这个参数,那么 docker 会给容器随机起一个格式类似于 gracious_brahmagupta 的名字。
**-it** 是为了获得可交互的 Shell 所必须的。**-i** 会将容器的 init(主进程,这里是 **/bin/bash**)的标准输入与 **docker** 这个程序的标准输入相连接;而 **-t** 会告知主进程输入为终端(TTY)设备。
在执行以上命令之后,你会获得一个 Ubuntu 20.04 的容器环境,退出 Shell 之后容器就会被销毁。
如果没有加上 --rm,退出后可以使用 **docker ps -a** 查看系统中所有的容器。
$ sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
39d8ef1d4acf ubuntu:20.04 "bash" 4 seconds ago Exited (0) 2 seconds ago ubuntu-container
之后使用 docker start 启动容器。
$ sudo docker start -ai ubuntu-container #使用name或容器ID都可以
root@39d8ef1d4acf:/#
-a 代表连接输出以及信号。最后的 ubuntu-container 代指我们刚刚创建的那个容器。也可以输入容器的 ID 来启动(不需要输入完整的 ID,只需要前几位即可):
$ sudo docker start -ai 39d
root@39d8ef1d4acf:/#
如果忘记加上了参数直接启动,也可以使用 **docker attach** 将容器的主进程的输入输出接上。
- 如果附加到一个已经附加了其他会话的容器,所有会话将共享相同的标准输入、输出和错误输出。这意味着多个会话中的输出可能会混杂在一起。
- 使用 docker attach 进行交互时,需要注意正确设置分离键序列,以确保能够安全地分离而不停止容器。
- docker attach –detach-keys=“ctrl-c” my_container
- 附加到 my_container 容器,并设置 ctrl-c 为分离键序列。
$ sudo docker attach ubuntu-container
root@39d8ef1d4acf:/#
docker exec 也可以完成相似的事情:它可以在容器中执行指定的命令(当然也包括 Shell 了)。
$ sudo docker exec -it ubuntu-container bash
root@39d8ef1d4acf:/#
由于 **docker exec** 创建的进程不是主进程,退出后容器也不会退出,适合需要调试容器的场合。
与 docker start 相对应,docker stop 可以关闭一个容器,docker rm 可以删除一个容器。
$ sudo docker stop ubuntu-container
39d
$ sudo docker rm ubuntu-container
39d
在 MkDocs 容器中构建本书
- 从 GitHub 上获取本书源码:
git clone https://github.com/ustclug/Linux101-docs.git docker run --rm -v ${PWD}/Linux101-docs:/docs -p 8000:8000 squidfunk/mkdocs-material
在执行完成之后,可以使用浏览器访问本地的 8000 端口,以查看构建结果。
这里多出了两个参数:
-v: 代表将本地的文件(夹)「挂载」(实际是 bind mount)到容器的对应目录中(这里是/docs)。注意这个参数只接受绝对路径,所以这里读取了PWD这个变量,通过拼接的方式拼出绝对路径。-p 8000:8000: 代表将容器的 8000 端口暴露在主机的 8000 端口上,否则容器外部访问不了 8000 端口。- 另外,我们不需要在终端中与容器中的进程进行交互,所以没有设置
-it参数。
构建自己的 Docker 镜像¶
手工构建镜像¶
docker commit 命令可以从当前运行的容器新建镜像。以下是一个简单的例子:
$ sudo docker run -it ubuntu
root@82d245a5a4a1:/# apt update && apt install curl
(输出省略)
root@82d245a5a4a1:/# exit
exit
$ sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
82d245a5a4a1 ubuntu "/bin/bash" 2 minutes ago Exited (0) 23 seconds ago laughing_elgamal
$ sudo docker commit 82d245a5a4a1
sha256:fe0a84d81b867949b27bacec4794303852b05ae76df14818bae85751b14f6e20
$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> fe0a84d81b86 54 seconds ago 116MB
$ sudo docker save fe0a84d81b86 > example.tar
$ ls -lh example.tar
-rw-r--r-- 1 ustc ustc 114M Feb 10 17:48 example.tar
得到的 example.tar 即为我们的 Docker 镜像。可以使用 docker load < example.tar 的方式在其他环境中加载。但是,从可维护性等方面考虑,我们更推荐以下使用 Dockerfile 的做法。
使用 Dockerfile 自动化构建
Dockerfile 是构建 Docker 镜像的标准格式,下面会举一些例子。我们会基于这些例子简单介绍 Dockerfile 的语法。
构建简单的交叉编译环境
这个例子尝试使用 Debian 仓库中的 RISC-V 交叉编译工具链与 QEMU 模拟器构建一个简单的用于交叉编译的环境。
FROM debian:buster-slim
RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list && \
apt update && apt install -y --no-install-recommends \
gcc-riscv64-linux-gnu g++-riscv64-linux-gnu libc6-dev-riscv64-cross \
binutils-riscv64-linux-gnu libstdc++-dev-riscv64-cross \
qemu-system-misc qemu-user-static qemu-user binfmt-support \
fish vim
WORKDIR /workspace/
ENV QEMU_LD_PREFIX=/usr/riscv64-linux-gnu/
CMD ["fish"]
通过使用 docker build,我们可以构建出镜像。
sudo docker build -t riscv-cross:example .
<font style="color:#A58F04;">-t </font>riscv-cross:example 代表为这个镜像打上 riscv-cross:example 的标签。构建完成后,使用 docker run 执行即可:
$ sudo docker run -v ${PWD}/workspace:/workspace -it riscv-cross:example
Welcome to fish, the friendly interactive shell
root@dec3d33003ee /workspace# vim helloworld.c
root@dec3d33003ee /workspace# riscv64-linux-gnu-gcc helloworld.c
root@dec3d33003ee /workspace# qemu-riscv64 ./a.out
Hello, world!
从这个例子中,我们可以看到 Dockerfile 的一些指令:
**<font style="color:#4C16B1;">FROM</font>**定义了基础镜像,之后执行的命令都是在基础镜像之上进行操作。如果希望基础镜像为空,可以使用FROM scratch。**<font style="color:#5C8D07;">RUN</font>**指定了在镜像中执行的命令。可以注意到,我们将多个命令合并在了一起执行,这有助于减小镜像的冗余大小。**<font style="color:#A58F04;">WORKDIR</font>**可以切换当前的所在的工作目录。**<font style="color:#C75C00;">ENV</font>**指定了当前的环境变量。**<font style="color:#AD1A2B;">CMD</font>**指定了容器启动时执行的命令。
Docker 在根据 Dockerfile 构建时,会从上到下执行这些指令,每条指令对应镜像的一层。Docker 容器镜像的独特之处就在于它的分层设计:在构建镜像时每层的更改会叠加在上一层上(这意味着,上一层的所有数据仍然会保留,即使在新的一层删除了);如果某一层已经存在,Docker 会直接使用这一层,节约构建的时间和占用的空间。


在生产环境中运行使用 Flask 编写的简单网站¶
Flask 是一个知名的 Python web 框架。本例子包含了一个运行 Flask 编写的网站的简单 Dockerfile(不包含数据库等部分):
FROM tiangolo/uwsgi-nginx-flask:python3.8
RUN pip3 config set global.index-url https://mirrors.bfsu.edu.cn/pypi/web/simple
RUN pip3 install pyopenssl
COPY ./app /app
这里使用了 COPY 指令,将本地的 app 目录复制进容器镜像的 /app 中。
使用 Docker Compose 自动运行容器¶
Docker Compose 是一个方便的小型容器编排工具。如果前面安装的是 docker.io 软件包,那么系统中可能未安装 docker-compose,使用以下命令安装:
$ sudo apt install docker-compose
使用 Docker Compose 创建 WordPress 博客¶
WordPress 是一个知名的博客应用。本例子使用 Docker Compose,创建了一个使用 MySQL 数据库的 WordPress。
新建一个文件夹,在其中放入一个名为 docker-compose.yml 的配置文件:
version: "3"
services:
db:
image: mysql:5.7
container_name: wordpress_db
restart: always
volumes:
- ./mysql:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: linux101-test
MYSQL_DATABASE: wordpress
MYSQL_ROOT_HOST: "%"
wordpress:
image: wordpress:latest
container_name: wordpress
restart: always
ports:
- "80:80"
depends_on:
- db
volumes:
- ./wp-content:/var/www/html/wp-content
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: root
WORDPRESS_DB_PASSWORD: linux101-test
在文件夹中运行 docker-compose up 命令即可启动样例,在 127.0.0.1:80 上即可看到 WordPress 的初始化界面。用 docker-compose up -d 命令可以让容器分离(detach)命令行。运行完成后,可以使用 docker-compose down 停止并删除容器。