Docker 使用

参考:

Practice

保持容器运行的小技巧

使用 tail 跟踪 /dev/null 文件:

docker run -d ubuntu bash -c "tail -f /dev/null"

Persisting data | Docker Docs

host 网络模式

巨坑!host 网络模式只支持 Linux。不支持 Windows 和 macOS。

Networking using the host network: The host networking driver only works on Linux hosts, and is not supported on Docker Desktop for Mac, Docker Desktop for Windows, or Docker EE for Windows Server.

image

管理 image

docker search hello-world  # 搜索 image

docker pull hello-world
# docker image pull library/hello-word
docker image ls  # 列出所有 image
docker images

docker image rm <img>  # 删除 image
docker rmi <img>

image 可以在同一 CPU 架构的任何操作系统上运行

build image

docker build -t <name> .  # . 表示 Dockerfile 所在的目录
# docker image build -t <name>:<tag> .

docker history <image>  # 查看镜像中创建镜像层的命令

-t: tag,相当于一个指向镜像的指针

每一个 image 都是由一系列的 layer 组成的。Dockerfile 中的每条命令都会创建一个新的 layer

docker history <image> 命令可以查看 image 中用于创建每个 layer 的命令。在命令的输出中,最底层的是 base image,最顶层的是 top layer。通过这个命令也可以看到每个 layer 的大小,帮助我们诊断大体积的 image

Dockerfile

FROM <image>   # 基础镜像
RUN <command>  # 在基础镜像上运行命令
COPY <src> <dest>  # 将本地文件复制到镜像中
EXPOSE <port>  # 暴露端口
CMD <command>  # 容器启动时运行的命令

每次 RUN 的执行都是在一个新的镜像层 layer 上进行的,所以,如果你的镜像中有多个 RUN 命令,那么每个 RUN 都会创建一个新的镜像层,这样会导致镜像体积变大。

常用基础镜像:ubuntu, alpine, node:14, python, golang, openjdk:11-jdk-slim, nginx:1.21.0-alpine

Language-specific guides | Docker Guides

Dockerfile reference

发布 image

# 给 image 打 tag (创建一个新的 tag 并将该 image id 与 tag 关联)
docker tag old:0.0.1 username/new:0.0.1
# docker image tag old:0.0.1 username/new:0.0.1

# 发布
docker push username/new:0.0.1
# docker image push username/new:0.0.1

container

管理 container

docker ps -a # 列出所有 container,包括终止运行的
# docker container ls -a # 同上

docker rm <container>  # <container> 可以是 tag 或者 id
# docker container rm <container>

常用命令:

docker rm -f <container>  # 强制删除一个容器(正在运行的容器也可以删除)

运行

docker run hello-world  # 若 image 不存在则自动执行 docker pull 命令
# docker container run hello-world

其他运行选项

docker run -it ubuntu  # -i: interactive -t: 分配一个伪 TTY
docker run -d ubuntu  # -d: 后台运行
docker run -p 80:80 nginx  # -p: 端口映射,将主机的 80 端口映射到容器的 80 端口
docker run -v /path/to/host:/path/to/container nginx  # -v: 挂载卷
docker run --name <name> nginx  # --name: 指定容器名
docker run --rm nginx  # --rm: 运行结束后自动删除容器

常用示例

  • -p 127.0.0.2:80:80: 将主机的 127.0.0.2:80 映射到容器的 80 端口
  • -v "$(pwd)":/app: 将当前目录挂载到容器的 /app 目录

停止

docker stop <container>  # 停止运行
docker kill <container>  # 强制停止

其他

docker start <container>  # 启动已经停止的 container

docker exec -it <container> bash  # 进入运行中的 container 的 shell

docker logs -f <container>  # 查看 container 的日志,-f: 实时查看

docker cp <container>:<path> <path>  # 从 container 中复制文件到主机

docker inspect <container>  # 查看 container 的详细信息

Advanced Topics

Volume Mounts

当你需要某个持久的地方来存储程序的数据时,卷挂载是一个很好的选择。(数据库)

docker volume create todo-db  # 创建一个 volume

docker run -dp 3000:3000 --mount type=volume,src=todo-db,target=/etc/todos getting-started  # 挂载 volume
docker volume ls
docker volume inspect todo-db  # 查看 volume 详细信息

Persist the DB | Docker Docs

Bind Mounts

绑定挂载对于开发环境来说是一个很好的选择。

docker run -it --mount type=bind,src="$(pwd)",target=/src ubuntu bash

也可以使用 -v--volume 选项(不推荐):

docker run -it -v "$(pwd)":/src ubuntu bash

Use bind mounts | Docker Docs

Run your app in a development container

docker run -dp 3000:3000 \
    -w /app --mount type=bind,src="$(pwd)",target=/app \
    node:18-alpine \
    sh -c "yarn install && yarn run dev"

-w: Working Directory

相当于直接使用一个安装了 Node.js 的机器运行这个 node 项目。

当你完成对项目的修改,这时再使用 build 命令构造出最终的镜像:

docker build -t getting-started .

Run your app in a development container | Docker Docs

了解更多高级存储概念,查阅 Manage data in Docker | Docker Docs

Docker Compose

Multi-container apps | Docker Docs

Docker Compose | Docker Manuals

Docker Compose 工具 | 阮一峰的网络日志

docker network create todo-app  # 创建一个网络

# 启动 mysql
docker run -d \
     --network todo-app --network-alias mysql \  # 连接到网络并指定网络别名(在网络中的主机名)
     -v todo-mysql-data:/var/lib/mysql \  # 卷挂载,自动创建名为 todo-mysql-data 的 volume
     -e MYSQL_ROOT_PASSWORD=secret \  # 设置环境变量,ROOT 密码
     -e MYSQL_DATABASE=todos \  # 设置环境变量,数据库名
     mysql:8.0

docker run -it --network todo-app nicolaka/netshoot  # 启动一个网络调试工具
$ dig mysql

# start your dev-ready container
docker run -dp 3000:3000 \
   -w /app -v "$(pwd):/app" \  # 绑定挂载
   --network todo-app \  # 连接到网络
   -e MYSQL_HOST=mysql \  # 设置环境变量,数据库 IP/主机名
   -e MYSQL_USER=root \  # 设置环境变量,数据库用户名
   -e MYSQL_PASSWORD=secret \  # 设置环境变量,数据库密码
   -e MYSQL_DB=todos \  # 设置环境变量,数据库名
   node:18-alpine \
   sh -c "yarn install && yarn run dev"  # 启动命令
⚠️ 安全提示 在生产中不建议通过环境变量来配置连接设置。在大多数情况下,secrets 被以文件的形式加载到运行的容器中,比如说 MySQL 容器支持通过以 `_FILE` 为后缀的环境变量来指向含有环境变量值的文件,如 `MYSQL_PASSWORD_FILE` 会让 app 使用文件中的值作为连接密码。

Why you shouldn't use ENV variables for secret data - Diogo Mónica


Use Docker Compose | Docker Docs

compose.yaml

services:
  app:
    image: node:18-alpine
    command: sh -c "yarn install && yarn run dev"
    ports:
      - 3000:3000
    working_dir: /app
    volumes:     
      - ./:/app  # Docker Compose 中可以直接使用相对路径
    environment:
      MYSQL_HOST: mysql
      MYSQL_USER: root
      MYSQL_PASSWORD: secret
      MYSQL_DB: todos

  mysql:  # 定义的服务名会自动成为其网络别名
    image: mysql:8.0
    # docker run 的 -v 选项会自动创建不存在的 volume,
    # 但是在 compose 中需要先在顶层 volumes section 
    # 中声明 volume,然后再在 service config 中指定
    # 挂载点
    volumes:
      - todo-mysql-data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: todos

volumes:  # volumes section, 声明 volume
  todo-mysql-data:  # key: value,如果使用默认 volume 引擎的话不用给出 value

相对路径以 compose 文件所在的目录为基准
相对路径必须以 ... 开头
Docker Compose 会自动为你的应用程序栈创建一临时网络,因此你不用在 compose 文件中定义网络

Ports | Docker Docs

Volumes | Docker Docs

运行应用程序栈

docker compose up -d  # 启动,-d 选项表示后台运行
docker compose down  # 停止

docker compose logs -f  # 查看日志,-f 选项表示实时输出
docker compose logs -f app  # 只查看 app 服务的日志

在运行 docker compose down 命令时,默认不会删除 compose 文件中的命名 volume,如果想要删除它们,可以加上 --volumes 选项。

构建多平台镜像

docker build --platform linux/amd64 -t undefined443/course:amd64 .
docker build --platform linux/arm64 -t undefined443/course:arm64 .

Multi-platform builds | Docker Docs

# 查看现有的 builder
docker buildx ls

# 使用 docker-container 驱动创建一个 builder
# docker-container 提供了多平台 build 支持
docker buildx create --name mybuilder --driver docker-container --bootstrap

# 切换到刚刚创建的 builder
docker buildx use mybuilder

# 构建多平台镜像
docker buildx build --platform linux/amd64,linux/arm64 -t <username>/<image>:latest --push .

# 检视镜像
docker buildx imagetools inspect <username>/<image>:latest

# 使用 SHA256 摘要指定一个镜像变体
docker run --rm <username>/<image>@sha256:2b77acdfea5dc5baa489ffab2a0b4a387666d1d526490e31845eb64e3e73ed20 uname -m

构建镜像的最佳实践

Image-building best practices| Docker Docs

Layer caching

当镜像层发生改变时,所有下游镜像层都要重新构建。

# syntax=docker/dockerfile:1
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]

如果我们这样写 Dockerfile,那么每次我们修改源代码并重新构建镜像时都要重新安装依赖,因为源代码的改变会导致 COPY . . 层发生改变,从而导致其下的所有层都要重新构建。

为了能够缓存依赖,我们需要重新组织 Dockerfile 的结构:

# syntax=docker/dockerfile:1
FROM node:18-alpine
WORKDIR /app
COPY package.json yarn.lock ./  # 先将安装依赖所需的文件拷贝进来
RUN yarn install --production   # 安装依赖
COPY . .                        # 最后再将其他内容拷贝进来
CMD ["node", "src/index.js"]

在 Dockerfile 所在的目录下创建 .dockerignore 文件,并填入以下内容:

node_modules

.dockerignore file | Docker Docs

在第二个 COPY 命令执行时应该忽略 node_modules 文件夹,否则它会覆盖 RUN 命令所产生的文件。

这样,只要依赖没有发生改变,那么在重新构建时就不需要重新安装依赖,从而减少 buildpushpull、以及更新镜像所需的时间。

Multi-stage builds

使用多阶段构建,可以帮助我们:

  • build-time 依赖与 runtime 依赖分离
  • 通过只传送 app 需要运行的内容来减小整个 image 的体积

Maven/Tomcat 示例:

# syntax=docker/dockerfile:1
# build 阶段,使用 Maven 构建 Java 项目
FROM maven AS build
WORKDIR /app
COPY . .
RUN mvn package

# runtime 阶段,使用 Tomcat 运行 Java 项目
FROM tomcat
# 将 build 阶段构建的文件拷贝进来
COPY --from=build /app/target/file.war /usr/local/tomcat/webapps

最终的镜像只包含最后创建的阶段(可以使用 --target 标记覆盖)

只构建需要的镜像
DOCKER_BUILDKIT=1 docker build --no-cache -f Dockerfile --target stage2 .

通过 --target 选项指定要构建的阶段。

通过设置 DOCKER_BUILDKIT=1,可以只构建 target 阶段以及 target 依赖的阶段。

3 Simple Tricks for Smaller Docker Image | infoq

Dockerfile 多阶段构建 | yeasy

Multi-stage builds | Docker Docs

在 GitHub 发布 Docker 镜像

  1. 在 GitHub 上创建一个 Personal access token (classic),需要选中 write:packagesdelete:packages 权限

  2. 在 Docker CLI 中登录到 GitHub Container Registry

    export CR_PAT=<your_token>
    echo $CR_PAT | docker login ghcr.io -u <username> --password-stdin
    

    <your_token><username> 替换为你自己的 GitHub Token 和 GitHub 用户名

  3. 标记镜像并推送到 GitHub Container Registry(ghcr.io)

    docker tag <image>:<tag> ghcr.io/<username>/<image>:<tag>
    docker push ghcr.io/<username>/<image>:<tag>
    

参考:使用容器注册表 | GitHub Docs

将 Docker 镜像连接到 GitHub 仓库

在 GitHub 的 Profile 中,选择 Packages,找到刚刚推送的镜像,点击 Connect to a repository

或者,编辑镜像的 Dockerfile,加入以下内容:

LABEL org.opencontainers.image.source https://github.com/<username>/<repo>

<repo> 替换为你要推送的库

Trouble Shooting

alpine apk 换源

临时

apk add --repository http://mirrors.aliyun.com/alpine/v3.14/main/ --allow-untrusted --no-cache bash

永久

sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories

Dockerfile 中有些命令写在一个 RUN 命令中会导致奇怪问题发生。

在 Alpine 中使用 Python 安装依赖时,在安装 cffi 时报错:

fatal error: Python.h: No such file or directory

解决方法:

RUN apk add --no-cache --update --virtual .build-deps \
    --repository http://mirrors.aliyun.com/alpine/v3.14/main/ \
    python3-dev \
    py3-pip \
    make \
    g++ \
    gcc \
    libc-dev \
    linux-headers \
    libffi-dev \
    openssl-dev

python:alpine 安装了 python3-dev 后,就可以正常安装 cffi 了。

fatal error: Python.h: No such file or directory | Stack Overflow

What is .build-deps for apk add --virtual command | Stack Overflow

将 build 镜像中的 .venv 拷贝到 runtime 后找不到 .venv/bin/python

原因是 build 镜像中 .venv/bin/python -> /bin/local/python,而 runtime 镜像的 Python 在 /usr/bin/python3.10

需要在构建虚拟环境时指定 Python 安装位置:

RUN PIPENV_VENV_IN_PROJECT=1 pipenv --python /usr/bin/python3.10 && pipenv install

A perfect way to Dockerize your Pipenv Python application

Windows 目录挂载问题

如果要将 Windows 目录挂载到 Linux 容器中,需要开启目录共享功能。

Shared Drives | Docker Docs

进入设置:Resources > File sharing,添加要共享的目录。

如果没有该设置项,检查 General 下的 Use the WSL 2 based engine,确保取消勾选。

Check checkbox

Add shared folder

posted @ 2024-06-06 02:23  Undefined443  阅读(9)  评论(0编辑  收藏  举报