Docker学习笔记

Docker 学习笔记

Docker 是一个为开发、分发、运行应用程序而生的开放平台,Docker 可以让我们将基础环境和应用程序分离开来,以便可以快速交付软件。有了 Docker,我们可以在管理软件的同时管理基础环境。基于 Docker 分发、测试、部署的方法论,可以极大的降低编码与交付运行之间的延迟。

使用 Docker 可以很轻松的保证程序运行环境的一致性,减少因环境不一致导致的各种问题。各个 Docker 容器之间相互隔离,可以在一个宿主机上运行多个容器,各个容器都包含了应用程序需要的所有资源,可以很轻松的保证同一个容器在每个地方运行的结果都一致。

Debian12 安装 Docker

卸载旧版本

sudo apt remove docker docker-engine docker.io

安装 gpg 并添加 gpg 密钥

sudo apt install gnupg
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

添加软件源并更新

echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update

安装

sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin

设置开机自启动并启动

sudo systemctl enable docker
sudo systemctl start docker

验证

sudo docker run --rm hello-world

输出下面的内容则表示安装成功

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
719385e32844: Pull complete
Digest: sha256:dcba6daec718f547568c562956fa47e1b03673dd010fe6ee58ca806767031d1c
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

使用国内 docker 镜像源,创建 /etc/daemon.json 文件并写入下面的内容:

{
   "registry-mirrors": [
   "https://mirror.ccs.tencentyun.com"
  ]
}

参考:Debian 安装 Docker

Dockerfile 生成镜像

Docker 入门文档中的 Dockerfile:

# syntax=docker/dockerfile:1

FROM node:18-alpine # 构建镜像时使用的基础镜像
WORKDIR /app
COPY . . # 复制当前路径下的文件到 WORKDIR
RUN yarn install --production
CMD ["node", "src/index.js"] # 从镜像启动容器时默认执行的命令
EXPOSE 3000

镜像构建命令:

# . 代表 Dockerfile 文件所在路径
# -t 构建出来的镜像的可读名称
docker build -t getting-started .

从镜像启动容器:

# -d 后台运行
# -p 指定容器端口与宿主机端口的映射,宿主机端口:容器端口
docker run -dp 3000:3000 getting-started

查看容器的日志:

docker logs <container id>

查看所有的镜像:

docker image ls
docker images

查看所有的容器:

docker ps -a

停止容器:

docker stop <container-id-1> <container-id-2>

删除容器:

docker rm [-f] <container-id-1> <container-id-2>

Docker Hub 共享镜像

Docker Hub 是一个 Docker 镜像仓库,可以通过 Docker Hub注册 Docker 账号:docker hub,获取 Docker ID 并创建 Docker Repository(Public 类型) 后将自己生成的镜像推送到 Docker Hub 从而与他人共享镜像。

CLI 登陆 Docker Hub:

docker login -u <docker id>

生成镜像:

docker build -t <docker id>/<image name>:[tag name] .

将镜像推送到 Docker Hub:

# docker push <docker id>/<image-name>:[tag name]
docker push chenxii81/getting-started:v1.1.0

问题记录:

The push refers to repository [docker.io/library/getting-started]
cdc22a9416c1: Preparing
1455e46f1ebf: Preparing
b81bc62fafca: Preparing
885a5d40fc11: Preparing
1b6c3782871e: Preparing
b0e46d71a47b: Waiting
f1417ff83b31: Waiting
denied: requested access to the resource is denied

因为 build image 的时候镜像名未添加 docker id。参考: 【備忘録】docker pushしたら拒否された

运行容器:

docker run -dp 0.0.0.0:3000:3000 <docker id>/getting-started

映射到 0.0.0.0:3000 可以从外部网络访问,如果写成 -p 3000:3000 也是相同的效果。

访问:http://localhost:3000 可以看到 todo list.

DB 持久化

volume 持久化数据

在一个容器中创建/更新/删除的文件,对于从同一个镜像来的容器是不可见的。Volumes 可以实现将宿主机文件系统和容器文件系统的关联,容器对挂载的文件所做的操作会保存在宿主机文件系统中,如果将宿主机文件系统挂载到多个容器,那么各个容器可以看到相同的数据。

docker volume create <volume name>

查看卷:

docker volume ls

查看卷详细信息:

docker volume inspect <volume name>

删除卷:

docker volume rm <volume name>

启动容器时使用 --mount 指定挂载的卷:

docker run -dp 0.0.0.0:3000:3000 --mount type=volume,src=todo-db,target=/etc/todos chenxii81/getting-started
  • type: volume
  • src: volume name
  • target: 挂载到容器的位置

查看 volume 中的数据存储位置:

docker volume inspect <volume name>
[
    {
        "CreatedAt": "2023-09-02T11:29:50+08:00",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/todo-db/_data",  // 数据存储位置
        "Name": "todo-db",
        "Options": {},
        "Scope": "local"
    }
]

如果 volume 为空,docker 会将容器中的文件拷贝至 volume,如果 volume不为空,则将 volume 中的内容拷贝至容器中。

bind 持久化数据

bind 挂载是除 volume 之外将宿主机文件系统共享到容器的另一种方式。

如果宿主机中为空,则会清空容器中的目录。如果宿主机有文件,不要使用 bind,容器目录会被清空。

bind 和 volume 的区别:

Named volumes Bind mounts
Host location Docker chooses You decide
Mount example (using --mount) type=volume,src=my-volume,target=/usr/local/data type=bind,src=/path/to/data,target=/usr/local/data
Populates new volume with container contents Yes No
Supports Volume Drivers Yes No

bind 使用:

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

在宿主机目录中创建/删除文件,docker 容器中能即时看到文件变化。

Using bind mounts is common for local development setups. The advantage is that the development machine doesn’t need to have all of the build tools and environments installed. With a single docker run command, Docker pulls dependencies and tools.

将本地源码挂载到 Docker 开发环境中:

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

-w: 指定指定容器工作路径。

当挂载目录文件有变化时 nodemon 进程会自动重启容器内应用,修改源码可以在应用中即使看到。

多容器应用

创建/查看/删除 Docker 网络

考虑到系统的可扩展性、组件隔离、降低容器复杂度,最好在一个容器中只运行一个应用。多个容器之间通过 Docker 网络进行通信。

创建 Docker 网络

docker network create todo-app

查看 Docker 网络

docker network ls

查看具体的网络详情

docker network inspect todo-app

删除网络

docker network rm todo-app

启动容器时创建网络

docker run -d --network todo-app --network-alias mysql -v todo-mysql-data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=secret -e MYSQL_DATABASE=todos mysql

nicolaka/netshoot

nicolaka/netshoot 实际上是一个工具集提供了很多方便的网络问题解决工具,可以加速我们对于日常docker 以及k8s 网络问题的解决。

参考:nicolaka/netshoot - GitHub

运行 nicolaka/netshoot 容器

docker run -it --network todo-app nicolaka/netshoot

使用 DNS 工具 dig 命令查看具体容器的网络信息

dig mysql
; <<>> DiG 9.18.13 <<>> mysql
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 64589
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;mysql.				IN	A

;; ANSWER SECTION:
mysql.			600	IN	A	172.18.0.2 # 容器 IP

;; Query time: 16 msec
;; SERVER: 127.0.0.11#53(127.0.0.11) (UDP)
;; WHEN: Sun Sep 03 03:13:12 UTC 2023
;; MSG SIZE  rcvd: 44

将应用加入网络

docker run -dp 0.0.0.0:3000:3000 -w /app --mount type=bind,src="$(pwd)",target=/app --network todo-app -e MYSQL_HOST=mysql -e MYSQL_USER=root -e MYSQL_PASSWORD=secret -e MYSQL_DB=todos node:18-alpine sh -c "yarn install && yarn run dev"

Docker compose

docker compose 是一个用 YAML 文件来定义和分享多容器应用的工具。

启动单个容器时

# node
docker run -dp 0.0.0.0:3000:3000 -w /app -v "$(pwd)":/app --network mysql -e MYSQL_HOST=mysql -e MYSQL_DB=todos -e MYSQL_USER=root -e MYSQL_PASSWORD=secret sh -c "yarn install && yarn run dev" node:18-alpine

# mysql
docker run -d --network todo-app --network-alias mysql -v todo-mysql-data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=secret -e MYSQL_DB=todos mysql:8.0

在 compose.yaml 文件中定义

services:
  app:
    image: node:18-alpine
    command: sh -c "yarn install && yarn run dev"
    ports: 
      - 0.0.0.0:3000:3000
    working_dir: /app
    volumes:
      - ./:/app
    environment:
      MYSQL_DB: todos
      MYSQL_USER: root
      MYSQL_PASSWORD: secret
      MYSQL_HOST: mysql
  mysql:
    image: mysql:8.0
    volumes:
      - todo-mysql-data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: todos
volumes:
  todo-mysql-data: 

按照 compose.yaml 定义启动容器(不能按照依赖关系顺序启动,可能导致部分容器启动失败

docker compose up -d

查看日志

docker compose logs -f

docker 会自动创建 network 和 volume。

删除相关的容器(不会删除创建的 volume,如果要删除 volume,需要加 –volumes)

docker compose down --volumes

镜像构建最佳实践

只像镜像中复制必要的文件有助于缩小镜像的大小

# 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"]

可以使用 .dockerignore 文件

Multi-stage builds

Multi-stage builds are an incredibly powerful tool to help use multiple stages to create an image. There are several advantages for them:

  • Separate build-time dependencies from runtime dependencies
  • Reduce overall image size by shipping only what your app needs to run

构建 Maven/Tomcat 应用时,分离编译文件和运行时文件

# syntax=docker/dockerfile:1

FROM maven AS build
WORKDIR /app
COPY . .
RUN mvn package

FROM tomcat
COPY --from=build /app/target/file.war /usr/local/tomcat/webapps

分两个阶段构建,build 阶段打包,第二阶段复制第一阶段 build 中需要的文件,最终的镜像中不会包含 build 阶段。

特定场景最佳实践

Node.js 使用 Docker

Java 使用 Docker

参考资料: