关于html:加速开发流程的-Dockerfile-最佳实践

0次阅读

共计 5795 个字符,预计需要花费 15 分钟才能阅读完成。

开发流程
作为开发人员,咱们心愿将开发环境与生产环境尽可能地匹配,以确保咱们构建的内容在部署时可能失常工作。
咱们还心愿可能疾速开发,这意味着咱们心愿构建速度要快,也心愿能够应用调试器之类的开发工具。容器是整顿咱们的开发环境的一种好办法,然而咱们须要正确定义 Dockerfile 以便可能与咱们的容器疾速交互。
增量构建
Dockerfile 是用于构建容器镜像的一个申明清单。Docker 构建器将每个步骤的后果作为镜像层进行缓存的同时,缓存可能会有效,从而导致使缓存有效的步骤以及所有后续步骤都须要从新运行,并从新生成相应的层。
当 COPY 或 ADD 援用构建上下文中的文件发生变化时,缓存会生效。所以构建步骤的程序可能会对构建的性能产生十分大的影响。让咱们看一个在 Dockerfile 中构建 NodeJs 我的项目的示例。在这个我的项目中,在 package.json 文件中指定了一些依赖项,这些依赖项是在运行 npm ci 命令时获取的。
最简略的 Dockerfile 文件如下所示:
FROM node:lts

ENV CI=true
ENV PORT=3000

WORKDIR /code
COPY . /code
RUN npm ci

CMD [“npm”, “start”]
复制代码
每当构建上下文中的文件发生变化时,咱们依照上述构造构建 Dockerfile 都会导致在 COPY 这一行使得缓存生效。也就是说除了会破费很长时间得 package.json 文件以外的其余任何文件产生了变更得话,都将会从新获取依赖项搁置到 node_modules 目录上面去。
为了防止这种状况发送,只在依赖项产生变更时(即,当 package.json 或 package-lock.json 更改时)才从新获取依赖,咱们应该思考将依赖项装置与应用程序的构建和运行离开。
优化后得 Dockerfile 如下所示:
FROM node:lts

ENV CI=true
ENV PORT=3000

WORKDIR /code
COPY package.json package-lock.json /code/
RUN npm ci
COPY src /code/src

CMD [“npm”, “start”]
复制代码
应用这种拆散的形式,如果 package.json 或 package-lock.json 文件没有变更,则缓存将用于 RUN npm ci 指令生成的这一层。这意味着,当咱们编辑应用程序源代码并进行重建时,就不会从新下载依赖项,从而节俭了很多工夫🎉。
在主机和容器之间放弃实时加载
该技巧和 Dockerfile 并不间接相干,但咱们常常听到这样的问题:在容器中运行应用程序并在主机上从 IDE 批改源代码时,如何放弃代码的热更新?
在咱们这里的示例,咱们须要将咱们的我的项目目录挂载到容器中,并传递一个环境变量来启用 Chokidar,该我的项目封装了 NodeJS 文件的更改事件。运行命令如下所示:
$ docker run -e CHOKIDAR_USEPOLLING=true -v ${PWD}/src/:/code/src/ -p 3000:3000 repository/image_name
复制代码
这里咱们通过 -v 将宿主机下面的代码目录挂载到容器中,当宿主机上的代码有任何变更时都会在容器中进行实时加载更新。
构建一致性
Dockerfile 最重要的事件之一就是从雷同的构建上下文(源,依赖项…)构建完全相同的镜像。
这里咱们将持续改良上一部分中定义的 Dockerfile。
从源上进行统一构建
如上一节所述,咱们能够通过在 Dockerfile 形容中增加源文件和依赖项并在其上运行命令来构建应用程序。
然而在后面的示例中,其实咱们每次运行 docker build 时都无奈确认生成的镜像是否雷同,为什么呢?因为每次 NodeJS 公布后,lts 标签就会指向 NodeJS 镜像的最新 LTS 版本,该版本会随着工夫的推移而变动,并可能带来重大变动。所以咱们能够通过对根底映像应用确定的标签来轻松解决此问题。如下所示:
FROM node:13.12.0

ENV CI=true
ENV PORT=3000

WORKDIR /code
COPY package.json package-lock.json /code/
RUN npm ci
COPY src /code/src

CMD [“npm”, “start”]
复制代码
在上面咱们还将看到应用特定标签的根底镜像还有其余长处。
多阶段和匹配适合的环境
咱们针对开发构建保持一致,然而针对生产环境如何来做到这一点?
从 Docker 17.05 开始,咱们能够应用多阶段构建来定义生成最终镜像的步骤。应用 Dockerfile 中的这种机制,咱们能够将用于开发流程的镜像与用于生产环境的镜像离开,如下所示:
FROM node:13.12.0 AS development

ENV CI=true
ENV PORT=3000

WORKDIR /code
COPY package.json package-lock.json /code/
RUN npm ci
COPY src /code/src

CMD [“npm”, “start”]

FROM development AS builder

RUN npm run build

FROM nginx:1.17.9 AS production

COPY –from=builder /code/build /usr/share/nginx/html
复制代码
当咱们看到 FROM…… AS 这样的指令就能够晓得是多构建阶段。咱们当初有开发、构建和生产 3 个阶段。通过应用 –target 标记构建特定的开发阶段的镜像,咱们能够持续将容器用于咱们的开发流程。
$ docker build –target development -t repository/image_name:development .
复制代码
同样还能够这样运行:
$ docker run -e CHOKIDAR_USEPOLLING=true -v ${PWD}/src/:/code/src/ repository/image_name:development
复制代码
没有 –target 标记的 docker 构建将构建最终阶段,在咱们这里就是生产镜像。咱们的生产镜像只是一个 nginx 镜像,其中在后面的步骤中构建的文件被搁置在了对应的地位。
生产筹备
放弃生产环境的镜像尽可能精简和平安是十分重要的。在生产中运行容器之前,须要查看以下几件事。
没有更多最新镜像版本
正如咱们后面说的,应用特定的标签的构建步骤有助于使镜像的生成的唯一性。此外至多还有两个十分好的理由为镜像应用具体的标签:

能够很不便在容器编排零碎(Swarm,Kubernetes…)中找到所有运行有镜像版本的容器。

Search in Docker engine containers using our repository/image_name:development image

$ docker inspect $(docker ps -q) | jq -c‘.[] | select(.Config.Image == “repository/image_name:development”) |”(.Id) (.State) (.Config)”‘

“89bf376620b0da039715988fba42e78d42c239446d8cfd79e4fbc9fbcc4fd897 {\”Status\”:\”running\”,\”Running\”:true,\”Paused\”:false,\”Restarting\”:false,\”OOMKilled\”:false,\”Dead\”:false,\”Pid\”:25463,\”ExitCode\”:0,\”Error\”:\”\”,\”StartedAt\”:\”2020-04-20T09:38:31.600777983Z\”,\”FinishedAt\”:\”0001-01-01T00:00:00Z\”}
{\”Hostname\”:\”89bf376620b0\”,\”Domainname\”:\”\”,\”User\”:\”\”,\”AttachStdin\”:false,\”AttachStdout\”:true,\”AttachStderr\”:true,\”ExposedPorts\”:{\”3000/tcp\”:{}},\”Tty\”:false,\”OpenStdin\”:false,\”StdinOnce\”:false,\”Env\”:[\”CHOKIDAR_USEPOLLING=true\”,\”PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\”,\”NODE_VERSION=12.16.2\”,\”YARN_VERSION=1.22.4\”,\”CI=true\”,\”PORT=3000\”],\”Cmd\”:[\”npm\”,\”start\”],\”Image\”:\”repository/image_name:development\”,\”Volumes\”:null,\”WorkingDir\”:\”/code\”,\”Entrypoint\”:[\”docker-entrypoint.sh\”],\”OnBuild\”:null,\”Labels\”:{}}”

Search in k8s pods running a container with our repository/image_name:development image (using jq cli)

$ kubectl get pods –all-namespaces -o json | jq -c‘.items[] | select(.spec.containers[].image == “repository/image_name:development”)| .metadata’

{“creationTimestamp”:”2020-04-10T09:41:55Z”,”generateName”:”image_name-78f95d4f8c-“,”labels”:{“com.docker.default-service-type”:””,”com.docker.deploy-namespace”:”docker”,”com.docker.fry”:”image_name”,”com.docker.image-tag”:”development”,”pod-template-hash”:”78f95d4f8c”},”name”:”image_name-78f95d4f8c-gmlrz”,”namespace”:”docker”,”ownerReferences”:[{“apiVersion”:”apps/v1″,”blockOwnerDeletion”:true,”controller”:true,”kind”:”ReplicaSet”,”name”:”image_name-78f95d4f8c”,”uid”:”5ad21a59-e691-4873-a6f0-8dc51563de8d”}],”resourceVersion”:”532″,”selfLink”:”/api/v1/namespaces/docker/pods/image_name-78f95d4f8c-gmlrz”,”uid”:”5c70f340-05f1-418f-9a05-84d0abe7009d”}
复制代码

对于 CVE(常见破绽和披露),咱们能够疾速晓得是否须要修补容器和镜像。在咱们这里的示例,咱们能够指定咱们的开发和生产镜像应用 alpine 版本。

FROM node:13.12.0-alpine AS development

ENV CI=true
ENV PORT=3000

WORKDIR /code
COPY package.json package-lock.json /code/
RUN npm ci
COPY src /code/src

CMD [“npm”, “start”]

FROM development AS builder

RUN npm run build

FROM nginx:1.17.9-alpine

COPY –from=builder /code/build /usr/share/nginx/html
复制代码
应用官网镜像
您能够应用 Docker Hub 搜寻在 Dockerfile 中应用的根底镜像,其中一些是官网反对的镜像。咱们强烈建议应用这些镜像:

他们的内容曾经过验证
修复 CVE 后,它们会疾速更新

Docker Hub 中的 nginx 官网镜像
您能够增加 image_filter 申请查问参数来获取正式版本的镜像:
https://hub.docker.com/search…
复制代码
下面咱们应用的示例中均应用 NodeJS 和 NGINX 的官网镜像。
足够的权限!
无论是否在容器中运行的所有应用程序都应恪守最小特权准则,这意味着应用程序应仅拜访其所需的资源。
如果呈现歹意行为或谬误,以太多特权运行的过程可能会在运行时对整个零碎造成意外的结果。
用非特权用户身份来配置镜像自身也是非常简单的:
FROM maven:3.6.3-jdk-11 AS builder
WORKDIR /workdir/server
COPY pom.xml /workdir/server/pom.xml
RUN mvn dependency:go-offline

RUN mvn package

FROM openjdk:11-jre-slim
RUN addgroup -S java && adduser -S javauser -G java
USER javauser

EXPOSE 8080
COPY –from=builder /workdir/server/target/project-0.0.1-SNAPSHOT.jar /project-0.0.1-SNAPSHOT.jar

CMD [“java”, “-Djava.security.egd=file:/dev/./urandom”, “-jar”, “/project-0.0.1-SNAPSHOT.jar”]
复制代码
只需创立一个新组,向其中增加一个用户,而后应用 USER 指令,咱们就能够应用非 root 用户运行容器了。

正文完
 0