2025-03-29 14:35:49 +08:00

1201 lines
28 KiB
Markdown
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# docke基础
相关教程:
https://yeasy.gitbook.io/docker_practice/
https://www.runoob.com/docker/docker-tutorial.html
https://yeasy.gitbook.io/docker_practice/
## 简介
> Docker 是一个应用打包、分发、部署的工具
> 你也可以把它理解为一个轻量的虚拟机,它只虚拟你软件需要的运行环境
## 概念
* 镜像:理解成软件安装包
* 容器:软件安装的环境
## 安装docker
* centos安装 https://docs.docker.com/engine/install/#server
* arch安装
```bash
# manjaro/arch: 可以用yay安装
yay -S docker
# 启动服务
sudo systemctl start docker
# 添加开机自启
sudo systemctl enable docker.service
# 取消开机自启
sudo systemctl disable docker.service
```
* 配置国内镜像
https://gist.github.com/y0ngb1n/7e8f16af3242c7815e7ca2f0833d3ea6
https://dockerpull.com/ https://dockerproxy.cn
```sh
# 不配置镜像源, 直接加上前缀
docker pull dockerpull.com/stilleshan/frpc:latest
```
```bash
# 配置镜像源
sudo vim /etc/docker/daemon.json
# 内容如下,保存
{
"registry-mirrors": [
"https://dockerpull.com",
"https://dockerproxy.cn"
]
}
sudo systemctl daemon-reload
sudo systemctl restart docker
```
* docker默认只有管理员才有权限管理 每次都得加sudo很麻烦
```bash
sudo usermod -aG docker $USER && newgrp docker # 将当前用户加入docker用户组且更新docker用户组
```
* 不使用root用户
```bash
# 默认情况下只有root用户和docker用户才能访问Docker引擎的unix socket
# 一般不直接使用root
# 1. 建立docker超级权限组
sudo groupadd docker
# 2. 把当前用户添加进docker超级权限组
sudo usermod -aG docker $USER
# 3. 退出终端并重新登录
# 4. 测试
docker run --rm hello-world
```
* centos7安装docker后启动失败:
```bash
# 提示 Failed to start Docker Application Container Engine.
vim /etc/docker/daemon.json
# 添加括号内的内容
{ "graph": "/mnt/docker-data", "storage-driver": "overlay" }
```
## 镜像
### 获取镜像
官方镜像库https://hub.docker.com/
```bash
docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]
# 例子,没加地址从默认镜像仓库下载
docker pull ubuntu:18.04
# 下载指定架构
docker pull example/image@sha256:your-selected-digest
```
### 列出镜像
```bash
docker image ls
# 或者 docker images, 多了个s
# 镜像ID 是镜像的唯一标识,一个镜像可以对应多个标签
# 镜像体积,本地展开后的大小
docker system df
# docker hub上显示的是压缩后会更小
# 如果docker镜像的名字为<none>
# 可能是名字被新镜像用了,这类镜像角虚悬镜像(dangling image)
docker image ls -f dangling=true
# 一般来说这些镜像失去作用了,可以随意删除
docker image prune
# 为了加速镜像构建,会利用中间层镜像,这些中间层镜像也没有标签,但不能删除
docker image ls -a
# 列出部分镜像
docker image ls ubuntu # 指定仓库名
# -f 过滤
docker image ls -f since=mongo:3.2 # 3.2之后的镜像
docker image ls -f before=mongo:3.2 # 3.2之前的镜像
```
### 删除镜像
```bash
docker image rm [选项] <镜像1> [<镜像2> ...]
# 简写
docker rmi [选项] <镜像1> [<镜像2> ...]
# <镜像> 可以是 镜像短ID、镜像长ID、镜像名 或者 镜像摘要
# 一般用短id, 即取id前3位就够了
# 删除pull过程或者create过程产生的临时镜像剩下的都是有用的镜像
docker rmi $(docker images --filter dangling=true -q)
```
### 运行镜像
* 命令安装 redis
```bash
docker run -d -p 6379:6379 --name redis redis:latest
```
* 使用docker-compose安装Word-press
需要先安装compose
```bash
# arch
yay -S docker-compose
# 查看帮助
docker-compose --help
```
编写配置文件
```yml
# docker-compose.yml
version: '3.1'
services:
wordpress:
image: wordpress
restart: always
ports:
- 8080:80
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: exampleuser
WORDPRESS_DB_PASSWORD: examplepass
WORDPRESS_DB_NAME: exampledb
volumes:
- wordpress:/var/www/html
db:
image: mysql:5.7
restart: always
environment:
MYSQL_DATABASE: exampledb
MYSQL_USER: exampleuser
MYSQL_PASSWORD: examplepass
MYSQL_RANDOM_ROOT_PASSWORD: '1'
volumes:
- db:/var/lib/mysql
volumes:
wordpress:
db:
```
使用配置文件
```bash
docker-compose up -d # -d是后台执行1
```
### 制作镜像
参考: https://www.bilibili.com/video/BV11L411g7U1?p=3
1. 准备自己的项目代码, 也可以直接下载示例代码: https://github.com/jqtmviyu/test-docker
2. 编写`dockerfile`文件
```bash
FROM node:11
MAINTAINER easydoc.net
# 复制代码
ADD . /app
# 设置容器启动后的默认运行目录
WORKDIR /app
# 运行命令,安装依赖
# RUN 命令可以有多个,但是可以用 && 连接多个命令来减少层级。
# 例如 RUN npm install && cd /app && mkdir logs
RUN npm install --registry=https://registry.npm.taobao.org
# CMD 指令只能一个,是容器启动后执行的命令,算是程序的入口。
# 如果还需要运行其他命令可以用 && 连接也可以写成一个shell脚本去执行。
# 例如 CMD cd /app && ./start.sh
CMD node app.js
```
[Dockerfile文档](https://docs.docker.com/engine/reference/builder/#run)
> 实用技巧:
> 如果你写 Dockerfile 时经常遇到一些运行错误,依赖错误等,你可以直接运行一个依赖的底,然后进入终端进行配置环境,成功后再把做过的步骤命令写道 Dockerfile 文件中,这样编写调试会快很多。
> 例如上面的底是`node:11`,我们可以运行`docker run -it -d node:11 bash`,跑起来后进入容器终端配置依赖的软件,然后尝试跑起来自己的软件,最后把所有做过的步骤写入到 Dockerfile 就好了。
> 掌握好这个技巧,你的 Dockerfile 文件编写起来就非常的得心应手了。
| 常见指令 | 作用 |
|---------------|------------------------|
| FROM | 指定镜像基础环境 |
| RUN | 运行自定义命令 |
| CMD | 容器启动命令或参数 |
| LABEL | 自定义标签 |
| EXPOSE | 指定暴露端口 |
| ENV | 环境变量 |
| ADD | 添加文件到镜像 |
| COPY | 复制文件到镜像 |
| ENTRYPOINT | 容器固定启动命令 |
| VOLUME | 数据卷 |
| USER | 指定用户和用户组 |
| WORKDIR | 指定默认工作目录 |
| ARG | 指定构建参数 |
3. 编译
官方文档: https://docs.docker.com/engine/reference/commandline/build/
```bash
docker build -t test:v1 .
# -t 设置镜像名字和版本号
# 指定dockerfile
docker build -f /path/to/Dockerfile -t test:v1 .
```
4. 运行
官方文档: https://docs.docker.com/engine/reference/run/
```bash
docker run -p 8080:8080 --name test-hello test:v1
# -p 映射容器内端口到宿主机
# --name 容器名字
# -d 后台运行
```
### 导出镜像
```bash
# 将修改后的容器重新做成一个镜像将这个镜像导出成一个tag包然后将这个tag包还原成一个新的镜像
docker commit 812a997f614a ubuntu:update
# 容器ID 新名字容器新tag随意起
#如:
docker commit 640c369d7093 nginx2:test
# 将变更后的tar包保存在当前路径下
docker save -o update1.tar ubuntu:update
# 将tar包转换成新的镜像
docker load --input tar镜像包
# 将容器的端口映射到宿主机的80端口上
docker run -itd --name nginx1 -p 80:80 还原后镜像的ID
```
### 镜像分层机制
* docker生成镜像时类似git那样, 会把每一步的变化增量记录为一层数据
* 启动多个容器时, 每个容器都有自己的读写层
* 多个镜像会共享相同的层级, 例如都基于alpine最新镜像, 那么会重复利用alpine这一层
```sh
# 查看dockerfile操作记录
docker image history 镜像名
# 可以查看数据的层级 Layers
docker image inspect 镜像名
# 查看读写层占用 virtual
docker ps -a -s
```
## 容器
### 启动
```bash
docker run -t -i ubuntu:18.04 /bin/bash
# -t 分配一个伪终端
# -i 让容器的标准输入保持打开
# 运行流程
# 检查本地是否存在指定的镜像,不存在就从 registry 下载
# 利用镜像创建并启动一个容器
# 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层
# 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
# 从地址池配置一个 ip 地址给容器
# 执行用户指定的应用程序
# 执行完毕后容器被终止
```
* 启动已终止的容器
```bash
docker container start <id>
```
* 重启
```bash
docker container restart <id>
```
* 守护态运行(后台运行)
`-d`参数
*并不是长久运行*
* 列出容器
```bash
docker container ls
```
* 查看容器信息
````bash
docker inspect <id>
````
* 获取容器输出信息
```bash
docker container logs <id>
```
### 终止
```bash
docker container stop <id>
# 终止后的容器可以该命令列出
docker container ls -a
```
### 查看细节
```sh
docker container inspect 容器名
# 包括网络
```
### 进入容器
```bash
# 使用 -d 容器会进入后台,想进入容器操作
docker exec -it <id> /bin/sh
# 退出
exit # 不会导致容器停止
```
### 导出容器快照
```bash
# 导出容器
$ docker container ls -a
$ docker export <CONTAINER ID> > ubuntu.tar
```
### 导入容器快照
```bash
cat ubuntu.tar | docker import - test/ubuntu:v1.0
docker image ls
```
也可以通过指定 URL 或者某个目录来导入,例如
```bash
docker import http://example.com/exampleimage.tgz example/imagerepo
```
用户既可以使用`docker load` 来导入镜像存储文件到本地镜像库,也可以使`docker import` 来导入一个容器快照到本地镜像库。这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也要大。此外,从容器快照文件导入时可以重新指定标签等元数据信息。
### 删除容器
```bash
docker container rm <id>
# 删除运行中的容器
docker container rm -f <id> # 会先停止再删除
# 清理所有终止状态的容器
docker container prune
```
## 数据管理
解决问题:
1. 运行docker后 修改代码需要重新build和run
2. 容器内的数据, 比如log删除后容器后不会保留
### 卷映射
> 一个可供一个或多个容器使用的特殊目录, 类似于 Linux 下对目录或文件进行 mount镜像中的被指定为挂载点的目录中的文件会复制到数据卷中仅数据卷为空时会复制
>
> * `数据卷` 可以在容器之间共享和重用
> * 对 `数据卷` 的修改会立马生效
> * 对 `数据卷` 的更新,不会影响镜像
> * `数据卷` 默认会一直存在,即使容器被删除
>
> > `数据卷` 是被设计用来持久化数据的它的生命周期独立于容器Docker 不会在容器被删除后自动删除 `数据卷`,并且也不存在垃圾回收这样的机制来处理没有任何容器引用的 `数据卷`。如果需要在删除容器的同时移除数据卷。可以在删除容器的时候使用 `docker rm -v` 这个命令
```bash
# 创建
docker volume create my-vol
# 列出
docker volume ls
# 详情
docker volume inspect my-vol
# 启动时挂在数据卷
docker run -d -P \
--name web \
-v my-vol:/usr/share/nginx/html \
nginx:alpine
# 创建一个名为 web 的容器,并加载一个 数据卷 到容器的 /usr/share/nginx/html 目录
# 查看容器数据卷
docker inspect <id>
# 在'Mounts'键下
# 删除
docker volume rm my-vol
# 删除无主的数据卷
docker volume prune
# 删除容器时顺带删除数据卷
docker rm -v <id>
```
```sh
# 数据卷位置
# - 在Linux上它通常在 `/var/lib/docker/volumes/` 目录下。
# - 在Windows上它通常在 `C:\ProgramData\Docker\volumes\` 目录下。
# - 在macOS上由于使用了轻量级虚拟机路径通常在虚拟机内部例如 `/var/lib/docker/volumes/`。
# 列出所有的卷
docker volume ls
# 显示卷的路径
docker volume inspect 卷名
```
### 删除僵尸卷
```bash
# 删除所有dangling数据卷即无用的Volume僵尸文件
docker volume rm $(docker volume ls -qf dangling=true)
# 删除所有dangling镜像即无tag的镜像
docker rmi $(docker images | grep "^<none>" | awk "{print $3}")
# 删除所有关闭的容器
docker ps -a | grep Exit | cut -d ' ' -f 1 | xargs docker rm
# 删除关闭的容器、无用的数据卷和网络
docker system prune
# 删除更彻底可以将没有容器使用Docker镜像都删掉
docker system prune -a
```
### 目录挂载
> 直接把宿主机目录映射到容器内,适合挂代码目录和配置文件。可挂到多个容器上
```bash
# -v 绝对路径:容器路径
docker run -p 8080:8080 --name test-hello -v D:/code:/app -d test:v1
# 容器内只读
docker run -p 8080:8080 --name test-hello -v D:/code:/app:ro -d test:v1
# 也可以挂单个文件
```
### 区别
* 写法: 卷映射 最明显的就是没有 / 路径
* 目录挂载只能单向映射到容器
* 卷映射: 当卷为空时, 把容器里的拷贝到卷里.不为空时, 卷映射到容器里
### 参数
```sh
docker run -d -v /etc/localtime:/etc/localtime:ro your-image
```
* `:ro` 只读
* `:rw` 读写
### 其他
`tmpfs mount` 适合存储临时文件,存宿主机内存中。不可多容器共享。
## 网络
### 外部访问容器
```bash
# -P 随机映射一个端口到内容容器开放的网络端口
docker run -d -P nginx:alpine
# -p 指定要映射的端口, 一一对应
docker run -d -p 80:80 nginx:alpine
# -p 可以使用多次
docker run -d \
-p 80:80 \
-p 443:443 \
nginx:alpine
# -p localhost:80:80 映射到指定地址的80到80端口
docker run -d -p 127.0.0.1:80:80 nginx:alpine
# -p localhost::80 映射到指定地址的所有端口到80端口
docker run -d -p 127.0.0.1::80 nginx:alpine
# -p localhost:80:80/udp 使用udp标记指定udp端口
docker run -d -p 127.0.0.1:80:80/udp nginx:alpine
# 查看映射端口配置
docker port fa 80
```
### 内部访问宿主机
```sh
docker network inspect bridge
# 一般是172.17.0.1
```
### 容器互连
> docker network创建一个桥接网络在docker run的时候将容器指定到新创建的桥接网络中这样同一桥接网络中的容器就可以互相访问
>
> > 如果有多个容器需要互相通信,建议用docker compose 创建
1. 创建一个名为my-net的网络
```bash
docker network create my-net
# -d 指定网络类型,有 bridge overlay, 默认 bridge
```
2. 运行一个容器在 `my-net` 网络中
```bash
docker run -it --name busybox1 --network my-net --network-alias busybox1 busybox sh
# --network 指定使用的网络
# --network-alias 在网络中的别名,在这个网络中的其他容器可以通过这个别名来访问该容器
# 如果没有设置别名, 那么容器名就是网络中的域名
# 如果容器已创建好但是之前没有指定自己的网络,则使用下面命令修改
# docker network connect --alias 网络中的别名 网络名 容器名
docker network connect --alias bosybox1 my-net busybox1
# 列出网络
docker network ls
# 查看网络中的容器
docker network inspect my-net
# 查看容器详情
docker inspect busybox1
```
3. 再运行另一容器并加入同一个网络
```bash
# 新开一个终端
docker run -it --name busybox2 --network my-net --network-alias busybox2 busybox sh
# 列出网络
docker network ls
# 查看网络中的容器
docker network inspect my-net
# 查看容器详情
docker inspect busybox1
```
4. 验证: 互相ping
```bash
# 在busybox1中
ping busybox2
# 在busybox2中
ping busybox1
```
### 配置dns
> 默认情况下, docker会使用主机上的`/etc/resolv.conf`
方法1:
```bash
# 在主机中修改 /etc/docker/daemon.json
{
"dns" : [
"114.114.114.114",
"8.8.8.8"
]
}
# 验证,用exec进入容器后
cat etc/resolv.conf
```
方法2:
```bash
# 添加启动参数
--dns=IP_ADDRESS
```
方法3:
```yaml
version: '3'
services:
your_service:
image: your_image
dns:
- 8.8.8.8
- 8.8.4.4
```
### docker contain 设置代理
https://docs.docker.com/network/proxy/
* 全局默认代理
```sh
# 在主机中新增`~/.docker/config.json`
{
"proxies": {
"default": {
"httpProxy": "http://proxy.example.com:3128",
"httpsProxy": "https://proxy.example.com:3129",
"noProxy": "localhost,127.0.0.0/8,192.168.0.0/16"
}
}
}
```
* 默认无代理
```sh
# default 为空
{
"proxies": {
"default": {
"httpProxy": "",
"httpsProxy": "",
"noProxy": "localhost,127.0.0.0/8,192.168.0.0/16"
}
}
}
```
* compose 自定义
```sh
# docker-compose中增加
environment:
- http_proxy=http://example:host
- https_proxy=http://example:host
- no_proxy="localhost,127.0.0.0/8,192.168.0.0/16"
- HTTP_PROXY=http://example:host
- HTTPS_PROXY=http://example:host
- NO_PROXY="localhost,127.0.0.0/8,192.168.0.0/16"
```
启动容器并通过`exec` 进入容器, 测试 `curl www.google.com`
### 设置额外的host
```yml
# compose 文件
extra_hosts:
- "bypass.duti.tech:172.67.157.165"
```
## docker-compose
> 多个容器,都要单独配置运行,并指定网络
>
> docker-compose可以把项目的多个服务集合到一起一键运行
官方文档
https://docs.docker.com/compose/
https://docs.docker.com/compose/compose-file/
1. docker-compose可能需要单独安装
```bash
# 查看版本
docker compose version
```
2. 编写配置文件, 参考示例`docker-compose.yml`
```yaml
services:
app:
build: ./
ports:
- 80:8080
volumes:
- ./:/app
environment:
- TZ=Asia/Shanghai
redis:
image: redis:5.0.13
volumes:
- redis:/data
environment:
- TZ=Asia/Shanghai
volumes:
redis:
```
* 容器默认时间不是北京时间,增加 TZ=Asia/Shanghai 可以改为北京时间
* `-` 表示数组
* 目录挂载不用额外写, 卷需要额外声明
* 指定某个网络也要额外声明
3. 在yml配置文件所在目录下运行
```sh
docker-compose up -d
```
4. 更多命令
查看运行状态:`docker-compose ps`
停止运行:`docker-compose stop`
重启:`docker-compose restart`
重启单个服务:`docker-compose restart service-name`
进入容器命令行:`docker-compose exec service-name sh`
查看容器运行log`docker-compose logs [service-name]`
**docker-compose 会自动创建网络可以相互通信, 不需要再指定**
## 发布
1. 到https://hub.docker.com/ 注册账号
2. 创建一个镜像库
3. 命令行登录账号:
`docker login -u username`
4. 新建一个tag名字必须跟你注册账号一样
`docker tag test:v1 username/test:v1`
5. 推上去
`docker push username/test:v1`
6. 部署试下
`docker run -dp 8080:8080 username/test:v1`
7. docker-compose 中也可以直接用这个镜像了
```
version: "3.7"
services:
app:
# build: ./
image: helloguguji/test:v1
ports:
- 80:8080
volumes:
- ./:/app
environment:
- TZ=Asia/Shanghai
redis:
image: redis:5.0.13
volumes:
- redis:/data
environment:
- TZ=Asia/Shanghai
volumes:
redis:
```
## 多tag
Docker 镜像可以同时拥有多个标签。每个标签实际上都是指向同一个镜像的指针。这意味着你可以为一个镜像添加多个标签,而这些标签都会指向相同的镜像内容
## 备份数据
如果你是用`bind mount`直接把宿主机的目录挂进去容器,那迁移数据很方便,直接复制目录就好了
如果你是用`volume`方式挂载的,由于数据是由容器创建和管理的,需要用特殊的方式把数据弄出来。
### 备份和导入 Volume 的流程
备份:
1. 运行一个 ubuntu 的容器,挂载需要备份的 volume 到容器,并且挂载宿主机目录到容器里的备份目录。
2. 运行 tar 命令把数据压缩为一个文件
3. 把备份文件复制到需要导入的机器
导入:
1. 运行 ubuntu 容器,挂载容器的 volume并且挂载宿主机备份文件所在目录到容器里
2. 运行 tar 命令解压备份文件到指定目录
### 备份 MongoDB 数据演示
1. 运行一个 mongodb创建一个名叫mongo-data的 volume 指向容器的 /data 目录
```bash
docker run -p 27018:27017 --name mongo -v mongo-data:/data -d mongo:4.4
```
2. 运行一个 Ubuntu 的容器挂载mongo容器的所有 volume映射宿主机的 backup 目录到容器里面的 /backup 目录,然后运行 tar 命令把数据压缩打包
```bash
docker run --rm --volumes-from mongo -v d:/backup:/backup ubuntu tar cvf /backup/backup.tar /data/
# --rm 容器在退出时将被删除
```
3. 最后你就可以拿着这个 backup.tar 文件去其他地方导入了。
### 恢复 Volume 数据演示
1. 运行一个 ubuntu 容器,挂载 mongo 容器的所有 volumes然后读取 /backup 目录中的备份文件,解压到 /data/ 目录
```bash
docker run --rm --volumes-from mongo -v d:/backup:/backup ubuntu bash -c "cd /data/ && tar xvf /backup/backup.tar --strip 1"
# 注意volumes-from 指定的是容器名字
# strip 1 表示解压时去掉前面1层目录因为压缩时包含了绝对路径
```
## 查看信息
```bash
# 查看当前运行中的容器
docker ps
# 查看 volume 列表
docker volume ls
# 查看网络列表
docker network ls
# 查看 docker 容器占用 CPU内存等信息
docker stats --no-stream
# Docker占用磁盘空间查看
docker system df
```
## 清理垃圾
### 清理无用的卷
```sh
#dangling的镜像为未被打标签和没有被任何容器引用的镜像。
docker volume rm $(docker volume ls -qf dangling=true)
```
### 删除未使用到的镜像
```sh
docker rmi $(docker images -q -f "dangling=true")
# 删除pull过程或者create过程产生的临时镜像剩下的都是有用的镜像
docker rmi $(docker images --filter dangling=true -q)
```
### 删除已停止的容器
```sh
docker rm $(docker ps -a -q)
# docker rm $(docker ps -aq)
```
### prune命令
```sh
docker system prune --help
# 默认不会删除 Volume 除非加 --volume 参数
docker system prune # 将没有容器使用Docker镜像都删掉
docker container prune # 清理无用容器
docker image prune # 清理无用镜像
docker volume prune # 清理无用数据卷
docker network prune # 清理无用网络
```
### 清理buildkit缓存
```sh
docker builder prune
```
## 限制资源
```sh
# 限制单个
cpus: '0.8'
network_mode: host
# 或者
deploy:
resources:
limits:
memory: 200M
cpus: "0.8"
```
https://lework.github.io/2020/01/10/docker-default-limit/
## 延迟启动
```sh
sudo vim /lib/systemd/system/docker.service
#在[Service] 标签下增加ExecStartPre=/bin/sleep 60 表示延迟60秒启动
```
## 启动脚本示例
```sh
entrypoint:
- bash
- -c
- |
set -e
echo 'Waiting for Postgres to be available'
export PGPASSWORD="$$POSTGRES_ENV_POSTGRES_PASSWORD"
maxTries=10
while [ "$$maxTries" -gt 0 ] && ! psql -h "$$DB_HOST" -U 'postgres' -c '\l'; do
sleep 1
done
echo
if [ "$$maxTries" -le 0 ]; then
echo >&2 'error: unable to contact Postgres after 10 tries'
exit 1
fi
exec /opt/files/startup.sh
```
## 客户端
podman desktop , lima, colima, orb
## 多平台打包
https://docs.docker.com/build/guide/multi-platform/
https://blog.yasking.org/a/docker-multi-arch-with-buildx.html
https://blog.imoe.tech/2023/01/05/docker-build-multiplatform-image/
### 仿真编译
> 速度慢, 性能不行
```sh
docker build --platform=linux/amd64,linux/arm64 -t jqtmviyu/auto_bangumi_tr:20240522 -t jqtmviyu/auto_bangumi_tr:20240522 . --push
```
### 交叉编译
1. 安装 Docker 的 buildx 插件
```sh
docker buildx install
# 查看支持哪些平台
sudo docker buildx ls
```
2. 安装二进制文件格式解释器
```sh
docker run --privileged --rm tonistiigi/binfmt --install all
```
3. 切换使用docker-container驱动
```sh
# --name 名称任意命令,不影响
# 当前tty生效
docker buildx create --name mybuilder --bootstrap --use
# 删除
docker buildx rm name
```
4. 构建多平台架构镜像
```sh
# 构建到缓存中
sudo docker buildx build --platform=linux/amd64,linux/arm64 -t jqtmviyu/auto_bangumi_tr:latest .
# 推送到仓库
# 需要先登录
sudo docker buildx build --platform=linux/amd64,linux/arm64 -t jqtmviyu/auto_bangumi_tr:latest --push .
# 保存
sudo docker buildx build --platform=linux/amd64,linux/arm64 -t jqtmviyu/auto_bangumi_tr:latest --output type=oci,dest=./auto_bangumi_tr.tar .
```
5. oci 镜像需要 skopeo 加载
```sh
sudo apt-get install skopeo
skopeo copy oci-archive:auto_bangumi_tr.tar docker-daemon:auto_bangumi_tr:latest
```
6. 查看dockhub镜像的Manifest
```sh
docker buildx imagetools inspect name:tag
```
## 设置代理
### dockerd
> "docker pull" 命令是由 dockerd 守护进程执行。而 dockerd 守护进程是由 systemd 管理。因此,如果需要在执行 "docker pull" 命令时使用 HTTP/HTTPS 代理,需要通过 systemd 配置。
```sh
sudo mkdir -p /etc/systemd/system/docker.service.d
vim /etc/systemd/system/docker.service.d/http-proxy.conf
===
[Service]
Environment="HTTP_PROXY=http://127.0.0.1:7890"
Environment="HTTPS_PROXY=http://127.0.0.1:7890"
Environment="NO_PROXY=localhost,127.0.0.1"
===
sudo systemctl daemon-reload
sudo systemctl restart docker
```
### docker 容器
```sh
vim ~/.docker/config.json
===
{
"proxies":
{
"default":
{
"httpProxy": "http://proxy.example.com:8080/",
"httpsProxy": "http://proxy.example.com:8080/",
"noProxy": "localhost,127.0.0.1,.example.com"
}
}
}
===
```
或者设置环境变量
| 环境变量 | docker run 示例 |
| ----------- | ------------------------------------------------- |
| HTTP_PROXY | --env HTTP_PROXY="http://172.17.0.1:7890/" |
| HTTPS_PROXY | --env HTTPS_PROXY="http://172.17.0.1:7890/" |
| NO_PROXY | --env NO_PROXY="localhost,127.0.0.1,.example.com" |
| | |
### docker build
```sh
docker build \
--build-arg "HTTP_PROXY=http://127.0.0.1:7890/" \
--build-arg "HTTPS_PROXY=http://127.0.0.1:7890/" \
--build-arg "NO_PROXY=localhost,127.0.0.1,.example.com" .
```
或者环境变量
| 环境变量 | Dockerfile 示例 |
| ----------- | ------------------------------------------------ |
| HTTP_PROXY | ENV HTTP_PROXY="http://proxy.example.com:8080/" |
| HTTPS_PROXY | ENV HTTPS_PROXY="http://proxy.example.com:8080/" |
| NO_PROXY | ENV NO_PROXY="localhost,127.0.0.1,.example.com" |
## 禁止ipv6
```sh
vim /etc/docker/daemon.json
{
"ipv6": false
}
```
## dockerfile优化
1. 使用更小的基础镜像, 比如 alpine.
2. 减少镜像层数, 比如 使用 && 符号将命令链接起来。
3. 多阶段构建
4. 善用 cache
5. pip 小技巧: `--no-cache-dir`
6. 设置 2 个 Python 的环境变量
* `ENV PYTHONDONTWRITEBYTECODE 1`
* `ENV PYTHONUNBUFFERED 1`
```sh
# 第一阶段安装GCC
FROM python:3.12.1-alpine as gcc_installer
# 安装GCC及其他依赖
RUN apk add --no-cache gcc musl-dev jpeg-dev zlib-dev libjpeg
# 第二阶段安装Python依赖
FROM gcc_installer as requirements_installer
# 设置工作目录
WORKDIR /app
# 只复制 requirements.txt充分利用 Docker 缓存层
COPY ./requirements.txt /app/
# 安装Python依赖
RUN pip install --no-user --prefix=/install -r requirements.txt
# 第三阶段:运行环境
FROM python:3.12.1-alpine
# 设置工作目录
WORKDIR /app
# 复制Python依赖
COPY --from=requirements_installer /install /usr/local
# 复制项目代码
COPY ./ /app
# 设置启动命令
CMD ["python", "/app/app.py"]
```
1. 使用alpine镜像
2. 多阶段构建基础镜像安装gcc和编译依赖接下来无需gcc直接复制基础镜像内的依赖复制项目代码开始运行
3. Alpine Linux 默认使用 musl libc 而不是 glibc