网站推广大概需要多少钱,温州网站建设公司哪个好,如何做网络推广推广,雄安做网站写在前面 分享一些 Dickerfile 构建镜像优化方式的笔记理解不足小伙伴帮忙指正 对每个人而言#xff0c;真正的职责只有一个#xff1a;找到自我。然后在心中坚守其一生#xff0c;全心全意#xff0c;永不停息。所有其它的路都是不完整的#xff0c;是人的逃避方式#…写在前面 分享一些 Dickerfile 构建镜像优化方式的笔记理解不足小伙伴帮忙指正 对每个人而言真正的职责只有一个找到自我。然后在心中坚守其一生全心全意永不停息。所有其它的路都是不完整的是人的逃避方式是对大众理想的懦弱回归是随波逐流是对内心的恐惧 ——赫尔曼·黑塞《德米安》 简单介绍
在 Docker 中常用的自定义构建新镜像的方式有两种
通过当运行的容器来构建新的镜像通过 Dockerfile 文件依托基础镜像来构建新的镜像
不管是那种方式自定义镜像 的原理都是一样通过镜像的分层设计创建读写层 修改配置 重新打包
这里和小伙伴们分享一些 Dockerfile 构建自定义镜像的优化方式所谓优化也可以理解为相对较优的构建方式对于 第一种我们这里简单介绍
通过当运行的容器来构建新的镜像一般在运行的镜像中做一些预制的操作比如内网环境没有依赖库没办法直接拉取需要的依赖我们可以在有网络的环境下拉取对应的依赖然后做成有依赖的基础镜像。
比如一个 python 镜像我们要在内网中使用但是内网环境没有 pip 源所以我们只能把对应的包先在外网环境下载做成镜像。
运行的容器来构建新的镜像
┌──[rootvms100.liruilongs.github.io]-[~]
└─$docker run -d python python -m http.server 33333
009033ff4c0155f81647b857c0bf8975ee750a13d7aa2584638af032aafa758b然后进入容器下载相关的依赖包之后生成镜像导出
┌──[rootvms100.liruilongs.github.io]-[~]
└─$docker commit bcdd82ca5b48 my-python:latest
sha256:cb7c9965c541dfc794f78eb06ae1c4af0c77bb87c92e5e6e768c7770eb61a5bb
┌──[rootvms100.liruilongs.github.io]-[~]
└─$docker save my-python:latest -o ./my-python.tar在操作上有些繁琐使用 Dockerfile 的方式可能方便一点
┌──[rootvms100.liruilongs.github.io]-[~]
└─$docker build -EOFFROM pythonRUN python -m pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simpleRUN python -m pip install psycopg2EOF
Sending build context to Docker daemon 2.048kB
Step 1/3 : FROM python--- a5d7930b60cc
Step 2/3 : RUN python -m pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple--- Running in 88140ad45551
Writing to /root/.config/pip/pip.conf
Removing intermediate container 88140ad45551--- df41fddd2cd2
Step 3/3 : RUN python -m pip install psycopg2--- Running in 1eddfbf7fa58
Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple
Collecting psycopg2Downloading https://pypi.tuna.tsinghua.edu.cn/packages/89/d6/cd8c46417e0f7a16b4b0fc321f4ab676a59250d08fce5b64921897fb07cc/psycopg2-2.9.5.tar.gz (384 kB)
Building wheels for collected packages: psycopg2Building wheel for psycopg2 (setup.py): started
.....14/44f32ab3b3f40f2e9a1a9ab8281a40ff4a911a930121c928b1
Successfully built psycopg2
Installing collected packages: psycopg2
.......
Removing intermediate container 1eddfbf7fa58--- 8791cb1dc692
Successfully built 8791cb1dc692
┌──[rootvms100.liruilongs.github.io]-[~]
└─$忘记打标签了。这里我们手动打一下。可以在 build 的时候通过 -t 命令指定
┌──[rootvms100.liruilongs.github.io]-[~]
└─$docker tag 8791cb1dc692 my-python:latest
┌──[rootvms100.liruilongs.github.io]-[~]
└─$docker images my-python
REPOSITORY TAG IMAGE ID CREATED SIZE
my-python latest 8791cb1dc692 8 minutes ago 927MBDockerfile 自定义镜像
构建常用指令温习
FROM基础镜像RUN制作镜像时执行的命令可以有多个每个命令一层ADD复制文件到镜像自动解压 (文件类型为: tar.gz 或 tar.bz2)COPY复制文件到镜像不解压MAINTAINER镜像创建者信息EXPOSE开放的端口ENV设置变量WORKDIR定义容器默认工作目录CMD: 容器启动时执行的命令仅可以有一条CMD.ENTRYPOINT:类似CMD指令的功能用于为容器指定默认运行程序从而使得容器像是一具单独的可执行程序
一些需要注意的事项 当 docker run 命令中声明了参数时Docker 守护程序会忽略 CMD 命令。 与CMD不同的是由ENTRYPOINT启动的程序不会被docker run命令行指定的参数所覆盖而且这些命令行参数会被当作参数传递给ENTRYPOINT指定的程序。不过docker run 命令的–entrypoint 选项的参数可覆盖ENTRYPOINT指令指定的程序 Dockfile中如果没有使用CMD指定启动命令则会继承上一个镜像的默认启动命令CMD 容器的默认启动命令有且只能有一条
根据Dockerfile生成新的镜像命令中,build 创建新的镜像-t 指定新镜像的名字和标签. 指定Dockerfile文件所在的目录
docker build -t imagename:latest Dockerfile所在目录容器和镜像之间的主要区别在于顶部 可写层。对容器的所有添加新数据或修改现有数据的写入都存储在此 可写层 中。删除容器时可写层也会被删除。基础镜像保持不变。
这里利用了 写时复制技术(COW,copy on write) , 对于开发的小伙伴可以结合 享元设计模式 理解对于运维的小伙伴可以结合 Openstack 组件 Glance 原理来理解
用通俗的话讲当修改时会把数据复制到容器层修改。当新增的时候直接在 容器层新增当删除时会屏蔽镜像层。
Docker 通过读取给定的指令来自动构建镜像。遵循特定的格式和指令集其中的 每一条指令在容器镜像中创建一个层。这些层是堆叠的每个层都是与前一层相比的变化的增量
这里我们以 redis:7 这个官方镜像为例,看看一个标准的 Dockerfile 如何书写可以看到镜像构建了 16 层。
┌──[rootvms107.liruilongs.github.io]-[/etc/systemd]
└─$docker history --humantrue redis:7
IMAGE CREATED CREATED BY SIZE COMMENT
19c51d4327cf 6 weeks ago /bin/sh -c #(nop) CMD [redis-server] 0B
missing 6 weeks ago /bin/sh -c #(nop) EXPOSE 6379 0B
missing 6 weeks ago /bin/sh -c #(nop) ENTRYPOINT [docker-entry… 0B
missing 6 weeks ago /bin/sh -c #(nop) COPY file:e873a0e3c13001b5… 661B
missing 6 weeks ago /bin/sh -c #(nop) WORKDIR /data 0B
missing 6 weeks ago /bin/sh -c #(nop) VOLUME [/data] 0B
missing 6 weeks ago /bin/sh -c mkdir /data chown redis:redis … 0B
missing 6 weeks ago /bin/sh -c set -eux; savedAptMark$(apt-m… 32MB
missing 6 weeks ago /bin/sh -c #(nop) ENV REDIS_DOWNLOAD_SHA06… 0B
missing 6 weeks ago /bin/sh -c #(nop) ENV REDIS_DOWNLOAD_URLht… 0B
missing 6 weeks ago /bin/sh -c #(nop) ENV REDIS_VERSION7.0.8 0B
missing 7 weeks ago /bin/sh -c set -eux; savedAptMark$(apt-ma… 4.13MB
missing 7 weeks ago /bin/sh -c #(nop) ENV GOSU_VERSION1.14 0B
missing 7 weeks ago /bin/sh -c groupadd -r -g 999 redis usera… 329kB
missing 7 weeks ago /bin/sh -c #(nop) CMD [bash] 0B
missing 7 weeks ago /bin/sh -c #(nop) ADD file:e2398d0bf516084b2… 80.5MB
┌──[rootvms107.liruilongs.github.io]-[/etc/systemd]
└─$docker history --humantrue redis:7 | wc -l
17涉及 两个 Dockerfile 文件构建的镜像
基础镜像构建
FROM scratch
ADD rootfs.tar.xz /
CMD [bash]reids 镜像构建
FROM debian:bullseye-slim# add our user and group first to make sure their IDs get assigned consistently, regardless of whatever dependencies get added
RUN groupadd -r -g 999 redis useradd -r -g redis -u 999 redis# grab gosu for easy step-down from root
# https://github.com/tianon/gosu/releases
ENV GOSU_VERSION 1.16
RUN set -eux; \savedAptMark$(apt-mark showmanual); \apt-get update; \apt-get install -y --no-install-recommends ca-certificates dirmngr gnupg wget; \rm -rf /var/lib/apt/lists/*; \dpkgArch$(dpkg --print-architecture | awk -F- { print $NF }); \wget -O /usr/local/bin/gosu https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch; \wget -O /usr/local/bin/gosu.asc https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc; \export GNUPGHOME$(mktemp -d); \gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \gpgconf --kill all; \rm -rf $GNUPGHOME /usr/local/bin/gosu.asc; \apt-mark auto .* /dev/null; \[ -z $savedAptMark ] || apt-mark manual $savedAptMark /dev/null; \apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportantfalse; \chmod x /usr/local/bin/gosu; \gosu --version; \gosu nobody trueENV REDIS_VERSION 7.0.9
ENV REDIS_DOWNLOAD_URL http://download.redis.io/releases/redis-7.0.9.tar.gz
ENV REDIS_DOWNLOAD_SHA f77135c2a47c9151d4028bfea3b34470ab4d324d1484f79a84c6f32a3cfb9f65RUN set -eux; \\savedAptMark$(apt-mark showmanual); \apt-get update; \apt-get install -y --no-install-recommends \ca-certificates \wget \\dpkg-dev \gcc \libc6-dev \libssl-dev \make \; \rm -rf /var/lib/apt/lists/*; \\wget -O redis.tar.gz $REDIS_DOWNLOAD_URL; \echo $REDIS_DOWNLOAD_SHA *redis.tar.gz | sha256sum -c -; \mkdir -p /usr/src/redis; \tar -xzf redis.tar.gz -C /usr/src/redis --strip-components1; \rm redis.tar.gz; \\
# disable Redis protected mode [1] as it is unnecessary in context of Docker
# (ports are not automatically exposed when running inside Docker, but rather explicitly by specifying -p / -P)
# [1]: https://github.com/redis/redis/commit/edd4d555df57dc84265fdfb4ef59a4678832f6dagrep -E ^ *createBoolConfig[(]protected-mode,.*, *1 *,.*[)],$ /usr/src/redis/src/config.c; \sed -ri s!^( *createBoolConfig[(]protected-mode,.*, *)1( *,.*[)],)$!\10\2! /usr/src/redis/src/config.c; \grep -E ^ *createBoolConfig[(]protected-mode,.*, *0 *,.*[)],$ /usr/src/redis/src/config.c; \
# for future reference, we modify this directly in the source instead of just supplying a default configuration flag because apparently if you specify any argument to redis-server, [it assumes] you are going to specify everything
# see also https://github.com/docker-library/redis/issues/4#issuecomment-50780840
# (more exactly, this makes sure the default behavior of save on SIGTERM stays functional by default)\
# https://github.com/jemalloc/jemalloc/issues/467 -- we need to patch the ./configure for the bundled jemalloc to match how Debian compiles, for compatibility
# (also, we do cross-builds, so we need to embed the appropriate --buildxxx values to that ./configure invocation)gnuArch$(dpkg-architecture --query DEB_BUILD_GNU_TYPE); \extraJemallocConfigureFlags--build$gnuArch; \
# https://salsa.debian.org/debian/jemalloc/-/blob/c0a88c37a551be7d12e4863435365c9a6a51525f/debian/rules#L8-23dpkgArch$(dpkg --print-architecture); \case ${dpkgArch##*-} in \amd64 | i386 | x32) extraJemallocConfigureFlags$extraJemallocConfigureFlags --with-lg-page12 ;; \*) extraJemallocConfigureFlags$extraJemallocConfigureFlags --with-lg-page16 ;; \esac; \extraJemallocConfigureFlags$extraJemallocConfigureFlags --with-lg-hugepage21; \grep -F cd jemalloc ./configure /usr/src/redis/deps/Makefile; \sed -ri s!cd jemalloc ./configure !$extraJemallocConfigureFlags ! /usr/src/redis/deps/Makefile; \grep -F cd jemalloc ./configure $extraJemallocConfigureFlags /usr/src/redis/deps/Makefile; \\export BUILD_TLSyes; \make -C /usr/src/redis -j $(nproc) all; \make -C /usr/src/redis install; \\
# TODO https://github.com/redis/redis/pull/3494 (deduplicate redis-server copies)serverMd5$(md5sum /usr/local/bin/redis-server | cut -d -f1); export serverMd5; \find /usr/local/bin/redis* -maxdepth 0 \-type f -not -name redis-server \-exec sh -eux -c \md5$(md5sum $1 | cut -d -f1); \test $md5 $serverMd5; \ -- {} ; \-exec ln -svfT redis-server {} ; \; \\rm -r /usr/src/redis; \\apt-mark auto .* /dev/null; \[ -z $savedAptMark ] || apt-mark manual $savedAptMark /dev/null; \find /usr/local -type f -executable -exec ldd {} ; \| awk // { print $(NF-1) } \| sort -u \| xargs -r dpkg-query --search \| cut -d: -f1 \| sort -u \| xargs -r apt-mark manual \; \apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportantfalse; \\redis-cli --version; \redis-server --versionRUN mkdir /data chown redis:redis /data
VOLUME /data
WORKDIR /dataCOPY docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT [docker-entrypoint.sh]EXPOSE 6379
CMD [redis-server]运行此镜像并创建一个容器时我们实际上在底层之上添加了一个新的可写层容器层。对正在运行的容器所做的所有更改例如写入新文件、修改现有文件和删除文件都将写入此可写容器层。
可以看到上面的 Dockerfile 文件很庞大通过这个文件我们来总结一些 在编写时需要注意的地方
Dockerfile 编写优化
使用小的基础镜像
在上面的 Dockerfile 中基础镜像使用有FROM debian:bullseye-slim, FROM scratch
scratch 是一个空镜像一般用不到在构建最基础的镜像的时候会用到。debian:bullseye-slim 是一个 debian 系统的 bullseye 版本的精简版镜像。看的出来使用的镜像很小。
使用较小的镜像可以更快地构建、推送和拉取 镜像。往往更安全因为只包含运行应用程序所需的必要库和系统依赖项。 尤其是在 CI/CD 等流水线中庞大的 基础镜像 在每个环节都要消耗一些时间从而使流水线的时间变得很长。镜像之间的区别主要在于底层的操作系统
镜像选择类型
Official Image官方镜像或者叫标准镜像一般由官方维护的镜像它是正确的选择但是可能不是最优的。镜像基于最新的稳定 Debian 操作系统发行版上面的 有 Dockerfile 构建完成的镜像即为 redis:7 的一个官网镜像。 Debian(bullseye/buster/stretch/jessie)不同的 Debian Linux 发行版镜像jessie(8.0)stretch(9.0) 是比较老旧的版本,buster(10.0) ,bullseye(11.0)为较新的版本
slim精简版它通常会安装运行特定工具所需的最小包
alpine基于 Alpine Linux项目专门为在容器内部使用而构建的操作系统。相比较 Debian 来说 Alpine 很小很小但是需要考虑一些时区兼容性问题。
scratch: 一个明确的空镜像特别是用于建立 “从头开始” 的镜像。
在选择最小基础镜像的同时要尽量避免安装不必要的软件包
指令链式运行
可以很明显的发现上面 Dockerfile 中的 RUN 指令很长
这是由于每个指令都会创建一个可缓存单元并构建一个新的中间镜像层。所以可以通过链接所有命令来避免过多层级。此外尽量避免链接过多的可缓存 RUN 命令因为这会导致创建大型缓存并最终导致缓存突发。
RUN set -eux; \\savedAptMark$(apt-mark showmanual); \apt-get update; \apt-get install -y --no-install-recommends \ca-certificates \wget \\dpkg-dev \gcc \libc6-dev \libssl-dev \make \; \rm -rf /var/lib/apt/lists/*; \\wget -O redis.tar.gz $REDIS_DOWNLOAD_URL; \..................变动的指令放到最后
Dockerfile 编写往往需要重复的构建每个层的构建都比较耗时但是 Docker 为了加快后续构建的速度会自动缓存每一层的构建当对应的层指令以及前面的指令没有发生变动时会直接使用缓存。当步骤对应指令更改时缓存不仅会针对该特定步骤失效还会使所有后续步骤失效。
所以 始终将最常更改的指令放在末尾。 会提高构建速度.
FROM python:3.9-slimWORKDIR /app
COPY requirements.txt .
RUN pip install -r /requirements.txt
COPY app.py .首选数组而不是字符串语法
我们可以通过两种不同的方式编写 最后的进程启动 命令 ENTRYPOINT
数组ENTRYPOINT [python,-m,http.server,33333]字符串ENTRYPOINT python -m http.server 33333
数组形式是首选。这是因为使用字符串形式会导致 Docker 使用 bash 运行您的进程这无法正确处理信号。由于大多数 shell 不处理子进程的信号因此如果使用 shell 格式CTRL-C生成 SIGTERM可能不会停止子进程。
COPY 而不是 ADD
如果有多个步骤使用上下文中的不同文件请 单独复制 它们而不是一次全部复制。这可确保每个步骤的生成缓存仅失效并在特别需要的文件发生更改时强制重新运行该步骤。
COPY requirements.txt /tmp/
RUN pip install --requirement /tmp/requirements.txt
COPY . /tmp/使用 .dockerignore
.dockerignore 文件的作用类似于 git 工程中的 .gitignore 。不同的是 .dockerignore 应用于 docker 镜像的构建它存在于 docker 构建上下文的根目录用来忽略不需要打入镜像的文件
.dockerignore 文件的写法和 .gitignore 类似支持正则和通配符具体规则如下
每行为一个条目以 # 开头的行为注释空行被忽略构建上下文路径为所有文件的根路径
.git
script
static
!README*.md从 stdin 标准输入构建
Docker 引擎能够通过本地或远程构建上下文通过 stdin 管道传输 Dockerfile 来构建镜像
在 Dockerfile 不需要将文件复制到镜像中COPY/ADD 将失败的情况下省略构建上下文非常有用并且可以提高构建速度因为不会将任何文件发送到 Docker 守护程序。适用于单纯的镜像构建
┌──[rootvms107.liruilongs.github.io]-[/etc/systemd]
└─$docker build -EOFFROM busyboxRUN echo hello worldEOF
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM busybox
latest: Pulling from library/busybox
5cc84ad355aa: Pull complete
Digest: sha256:5acba83a746c7608ed544dc1533b87c737a0b0fb730301639a0179f9344b1678
Status: Downloaded newer image for busybox:latest--- beae173ccac6
Step 2/2 : RUN echo hello world--- Running in c56fb8343c72
hello world
Removing intermediate container c56fb8343c72--- 95bc7e444353
Successfully built 95bc7e444353
┌──[rootvms107.liruilongs.github.io]-[/etc/systemd]
└─$利用多阶段构建
多阶段构建使我们能够通过利用构建缓存大幅减小最终镜像的大小而无需努力减少中间层和文件的数量。例如让我们看一下以下内容Dockerfile
FROM golang:1.18-alpine AS prebuild# Install tools required for project
RUN go get github.com/golang/dep/cmd/dep
COPY Gopkg.lock Gopkg.toml /go/src/project/
WORKDIR /go/src/project/
RUN go build -o /bin/project# This results in a single layer image
FROM scratch
COPY --fromprebuild /bin/project /bin/project
ENTRYPOINT [/bin/project]可以使用多个语句。每个指令都可以使用不同的基础并且每个指令都开始构建的新阶段。我们可以有选择地将伪影从一个阶段复制到另一个阶段在最终镜像中留下我们不想要的所有内容。
#syntaxdocker/dockerfile:1.4
FROM … AS build1
COPY –fromapp1 . /srcFROM … AS build2
COPY –fromapp2 . /srcFROM …
COPY –frombuild1 /out/app1 /bin/
COPY –frombuild2 /out/app2 /bin/博文部分内容参考
文中涉及参考链接内容版权归原作者所有如有侵权请告知 https://www.docker.com/
https://docs.docker.com/engine/reference/builder/
https://www.docker.com/blog/dockerfiles-now-support-multiple-build-contexts/
https://blog.devgenius.io/devops-in-k8s-write-dockerfile-efficiently-37eaedf87163 © 2018-2023 liruilongergmail.com, All rights reserved. 保持署名-非商用-相同方式共享(CC BY-NC-SA 4.0)