惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
Schneier on Security
Hugging Face - Blog
Hugging Face - Blog
V
Visual Studio Blog
博客园 - Franky
酷 壳 – CoolShell
酷 壳 – CoolShell
Last Week in AI
Last Week in AI
博客园 - 叶小钗
博客园_首页
阮一峰的网络日志
阮一峰的网络日志
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Application and Cybersecurity Blog
Application and Cybersecurity Blog
TaoSecurity Blog
TaoSecurity Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
J
Java Code Geeks
爱范儿
爱范儿
宝玉的分享
宝玉的分享
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
量子位
N
News and Events Feed by Topic
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Recent Commits to openclaw:main
Recent Commits to openclaw:main
SecWiki News
SecWiki News
MyScale Blog
MyScale Blog
AI
AI
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
博客园 - 【当耐特】
Security Archives - TechRepublic
Security Archives - TechRepublic
F
Fortinet All Blogs
V2EX - 技术
V2EX - 技术
T
Troy Hunt's Blog
有赞技术团队
有赞技术团队
W
WeLiveSecurity
Project Zero
Project Zero
T
Tor Project blog
Help Net Security
Help Net Security
L
LINUX DO - 最新话题
IT之家
IT之家
The Hacker News
The Hacker News
腾讯CDC
Schneier on Security
Schneier on Security
N
News and Events Feed by Topic
C
Cisco Blogs
博客园 - 聂微东
Webroot Blog
Webroot Blog
Forbes - Security
Forbes - Security
M
MIT News - Artificial intelligence
C
Cyber Attacks, Cyber Crime and Cyber Security
雷峰网
雷峰网
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
A
About on SuperTechFans

lvbibir's Blog

shell | 磁盘空间分析脚本 Linux cat 和 tee 命令写入文件 python | 使用 uv 管理你的 python 环境 Claude Code 完整配置指南 windows | mihomo 内核独立部署指南 linux | 磁盘扩容 wsl | 释放长久运行占用的磁盘空间 mysql | 线程上限问题处理 麒麟 7.6 安装谷歌 OTP 认证模块 suse 12sp5 升级 openssh 及 openssl suse 12sp5 部署 mysql 5.7 《谁的青春不迷茫》 docker | centos7 部署 docker vim | 基础配置和使用 windows | rime 输入法 & 雾凇方案 shell | sshpass 批量传输文件及执行命令 wsl | 原生 linux 方式安装 docker nodejs | fnm + pnpm 开发环境配置 wsl | 安装配置 miniconda 虚拟环境 wsl | 自动更新系统代理 wsl | bashrc 环境变量不正确加载的处理方法 wsl | win10 安装 wsl2 troubleshooting | ssh 成功但是 scp 失败 linux | 常用命令总结 docker 部署 piclist shell | 检测网站存活并自动钉钉告警 Zabbix | 监控端口连通性并自动追踪 TCP 路由 windows | 自定义开机快速启动项 windows | miniconda 配置 python 虚拟环境 Zabbix | 监控主机到指定 ip 的流量大小 shell | centos 初始化 loki (二) 部署 loki (一) 简介 prometheus (六) Alertmanager prometheus (五) 记录规则与告警规则 prometheus (四) 黑盒监控 prometheus (三) 服务发现 prometheus (二) 静态配置 prometheus (一) 简介及部署 traefik (四) TraefikService 服务 traefik (三) Middleware 中间件 traefik (二) ingressRoute 路由 traefik (一) 简介、部署和配置 kubernetes | Gateway API 简介及部署 linux | dns 配置文件中 search 和 options ndots 详解 kubernetes | command args 和 dockerfile 中的 ENTRYPOINT CMD kubernetes | statefulset 控制器详解 docker | dockerfile 指令详解 linux | kill 命令详解以及 linux 中的信号 troubleshooting | elasticsearch 安装插件报错 shell | if 条件判断 docker | 下载外网镜像的几种方式 python | 鬼谷子数学问题 steam挂刀教程 linux | history 命令的格式化输出 shell | 将本地镜像批量推送到 harbor windows | hosts 文件修复 kubernetes | configmap & secret kubernetes | RBAC 鉴权和 NetworkPolicy kubernetes | service & ingress kubernetes | 存储 kubernetes | 滚动升级和自动伸缩 kubernetes | 控制器 kubernetes | 日志 kubernetes | 调度 kubernetes | pod kubernetes | 杂记 linux | set 命令详解 ceph | openeuler 部署 ceph-v16 ceph | ceph-v16 离线安装解决方案 pxe 如何应对复杂的服务器硬件环境 python | 批量修改目录下文件名 《微习惯》 ceph | openeuler (aarch64) 部署 ceph-v16 troubleshooting | glibc 误升级后修复 pxe 安装配置大全 《人间失格》 跑步日常 [置顶] Hello, hugo! shell | 检索某 url 中所有文件的内容 shell | 不同执行方式的区别 shell | 开启 debug 模式 vscode | 常见问题 windows & linux 多网卡时设置默认路由以及添加静态路由 mysql (二) 主从复制原理 GTID 并行复制 mysql (一) 部署 mysql | 杂记 cicd | jenkins 部署 mall-swarm 项目 httpd 源码打包编译成 rpm 包 openssl 源码打包编译成 rpm 包 centos7 | 修改网卡名称 ceph | pool pg_num 配置 docker | 脚本方式批量导出/导入镜像 centos 密码尝试次数过多问题处理 centos7 | 升级内核至 5.10 CVE-1999-0526 ceph | centos7 部署 ceph-v12 python | 修改 pip 源 troubleshooting | 安装 cloud-init 后导致 ssh 连接失败 windows | autohotkey 常用脚本
docker | dockerfile 最佳实践
lvbibir · 2023-04-11 · via lvbibir's Blog

0 前言

本文参考以下内容:

在使用 Docker 的过程中,编写 Dockerfile 是非常重要的一部分工作。合理编写 Dockerfile 会使我们构建出来的 Docker image 拥有更佳的性能和健壮性

目标:

  • 更快的构建速度
  • 更小的 Docker 镜像大小
  • 更少的 Docker 镜像层
  • 充分利用镜像缓存
  • 增加 Dockerfile 可读性
  • 让 Docker 容器使用起来更简单

总结

  • 编写.dockerignore 文件
  • 容器只运行单个应用
  • 将多个 RUN 指令合并为一个
  • 基础镜像的标签不要用 latest
  • 每个 RUN 指令后删除多余文件
  • 选择合适的基础镜像 (alpine 版本最好)
  • 设置 WORKDIR 和 CMD
  • 使用 ENTRYPOINT (可选)
  • 在 entrypoint 脚本中使用 exec
  • COPY 与 ADD 优先使用前者
  • 合理调整 COPY 与 RUN 的顺序
  • 设置默认的环境变量,映射端口和数据卷
  • 使用 LABEL 设置镜像元数据
  • 添加 HEALTHCHECK

可以说每条 Dockerfile 指令都有相关的优化项,这里就不一一赘述了,下面仅列举一些常见且重要的设置

1 容器的优雅退出

众所周知,docker 容器本质上是一个个进程,进程的优雅退出需要考虑的是如何正确处理 SIGTERM 信号,关于这点在我的另一篇博文中介绍过 kill命令详解以及linux中的信号

无论是 docker stop 还是在 kubernetes 中使用容器,一般关闭容器都是向容器内的 1 号进程发送 SIGTERM 信号,等待容器自行进行资源清理等操作,等待时间 docker 默认 10s,k8s 默认 30s,如果容器仍未退出,则发送 SIGKILL 信号强制杀死进程

综上,我们只需要考虑 2 点

  1. 应用程序如何处理信号

这就需要在应用程序中定义对信号的处理逻辑了,包括对每个信号如何处理如何转发给子进程等。

  1. 应用程序如何获取信号

docker 容器的一号进程是由 CMD ENTRYPOINT 这两个指令决定的,所以正确使用这两个指令十分关键

CMDENTRYPOINT 分别都有 execshell 两种格式:

  • 使用 exec 格式时,我们执行的命令就是一号进程
  • 使用 shell 格式时,实际会以 /bin/sh -c command arg… 的方式运行,这种情况下容器的一号进程将会是 /bin/sh,当收到信号时 /bin/sh 不会将信号转发给我们的应用程序,导致意料之外的错误,所以十分不推荐使用 shell 格式

我们还可以使用 tini 作为 init 系统管理进程

官方地址:https://github.com/krallin/tini

Tini (Tiny but Independent) 是一个小型的、可执行的程序,它的主要目的是作为一个 init 系统的替代品,用于在容器中启动应用程序。

在容器中启动应用程序时,通常会使用 init 系统来管理进程。然而,由于容器的特殊性,传统的 init 系统可能无法完全满足容器化应用程序的需求。Tini 作为一个小巧而独立的程序,可以帮助解决容器启动时可能遇到的各种问题,如僵尸进程、信号处理等。

在 Docker 中使用 Tini 的主要意义在于提高容器的稳定性和可靠性。Tini 可以确保容器中的应用程序在启动和退出时正确处理信号,避免僵尸进程和其它常见问题的出现。此外,Tini 还可以有效地限制容器中的资源使用,避免应用程序崩溃或者占用过多的系统资源,从而提高容器的可用性和可维护性。

总之,使用 Tini 可以让容器中的应用程序更加健壮、稳定和可靠,这对于运行生产环境中的应用程序非常重要。

使用示例

FROM nginx
ENV TINI_VERSION=v0.19.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini  /tini
RUN chmod +x /tini
ENTRYPOINT ["/tini", "--", "/docker-entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]

Alpine Linux

RUN apk add --no-cache tini
# Tini is now available at /sbin/tini
ENTRYPOINT ["/sbin/tini", "--"]

NixOS

Debian

Arch Linux

2 RUN 指令

RUN 指令一般用于安装配置软件包等操作,通常需要比较多的步骤,如果每条命令都单独用 RUN 指令去跑会导致镜像层数非常多,所以尽可能将所有 RUN 指令拼接起来是当前的事实标准

也要将 RUN 指令中生产的一些附属文件删除以缩小最终镜像的大小

如下示例

FROM debian:stretch

RUN set -x; buildDeps='gcc libc6-dev make wget' \
    && apt-get update \
    && apt-get install -y $buildDeps \
    && wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \
    && mkdir -p /usr/src/redis \
    && tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
    && make -C /usr/src/redis \
    && make -C /usr/src/redis install \
    && rm -rf /var/lib/apt/lists/* \
    && rm redis.tar.gz \
    && rm -r /usr/src/redis \
    && apt-get purge -y --auto-remove $buildDeps

3 多阶段构建

很多时候我们的应用容器会包含 构建运行 两大功能,而运行所需要的依赖数量明显少于构建时的依赖,我们最终的 image 交付物有运行环境就足够了

在很多的场景中,我们都会制作两个 Dockerfile 分别用于构建和运行,文件交付起来十分麻烦

Docker Engine 17.05 中引入了多阶段构建,以此降低构建复杂度,同时使缩小镜像尺寸更为简单

如下示例,go 程序编译完后几乎不需要任何依赖环境即可运行

# 阶段1
FROM golang:1.16
WORKDIR /go/src
COPY app.go ./
RUN go build app.go -o myapp

# 阶段2,引用空镜像 scratch 
FROM scratch
WORKDIR /server
# 复制文件,通过编号引用,0 代表阶段 1
COPY --from=0 /go/src/myapp ./ 
CMD ["./myapp"]

上述例子可以修改一下,可读性更强

# 阶段1命名为builder
FROM golang:1.16 as builder
WORKDIR /go/src
COPY app.go ./
RUN go build app.go -o myapp

# 阶段2,引用空镜像 scratch 
FROM scratch
WORKDIR /server
# 复制文件,通过名称引用
COPY --from=builder /go/src/myapp ./ 
CMD ["./myapp"]

只构建某个阶段

构建镜像时,不一定需要构建整个 Dockerfile,我们可以通过 --target 参数指定某个目标阶段构建,比如我们开发阶段我们只构建 builder 阶段进行测试。

docker build --target builder -t builder_app:v1 .

使用外部镜像

COPY --from  httpd:latest /usr/local/apache2/conf/httpd.conf ./httpd.conf

从上一阶段创建新的阶段

# 阶段1命名为builder
FROM golang:1.16 as builder
WORKDIR /go/src
COPY app.go ./
RUN go build app.go -o myapp

# 阶段2,引用阶段1再进行一次构建
FROM builder as builder_ex
ADD dest.tar ./
...

以上