https://mengz.me/posts/docker...java
在進行應用容器化的實踐中,咱們可使用多種方式來建立容器鏡像,而使用Dockerfile是咱們最經常使用的方式。
並且在實現CI/CD Pipeline的過程當中,使用Dockerfile來構建應用容器也是必須的。 git
本文不具體介紹Dockerfile的指令和寫法,僅僅是在實踐中積累的一些寫好一個Dockerfile的小提示,體如今一下幾個方面:golang
首先來看看下面這個Dockerfilespring
FROM ubuntu:18.04 COPY . /app RUN apt-get update RUN apt-get -y install ssh vim openjdk-8-jdk CMD [「java」,」-jar」,」/app/target/app.jar」]
要減少構建的時間,那咱們能夠例如Docker構建的緩存特性,儘可能保留不常常改變的層,而在Dockerfile的指令中, COPY
和RUN
都會產生新的層,並且緩存的有效是與命令的順序有關係的。
在上面的Dockerfile中,COPY . /app
在RUN apt-get ...
以前,而COPY是常常改變的部分,因此每次構建都會到致使RUN apt-get ...
緩存失效。 docker
Tip-1 : 合理利用緩存,而執行命令的順序是會影響緩存的可用性的。 ubuntu
要減少構建時間,另外一方面是應該僅僅COPY須要的東西,對於上面這個Dockerfile的目的,應該僅僅須要COPY Java應用的jar文件。 vim
Tip-2 : 構建過程當中僅僅COPY須要的東西。 緩存
上面的Dockerfile對apt-get命令分別使用了兩個RUN指令,會生成兩個不一樣的層。 安全
Tip-3 : 儘可能合併最終鏡像的層數。 服務器
還有對於這個示例,咱們最終是想要一個JRE環境來運行Java應用,所以能夠選擇一個jre的鏡像來做爲基礎鏡像,這樣不用花時間再去安裝jdk。
Tip-4 : 選擇合適的基礎鏡像
這樣咱們能夠把Dockerfile寫成:
FROM ubuntu:18.04 RUN apt-get update \ && apt-get y install ssh vim openjdk-8-jdk COPY target/app.jar /app CMD [「java」,」-jar」,」/app/app.jar」]
進一步,咱們如何儘可能減少最終應用鏡像的大小,來加速咱們的CI構建,以及減少鏡像在網絡上傳輸的效率。
在上例中,ssh, vim
應該都是沒必要要的軟件包,它們會咱用鏡像的空間。
Tip-5 : 移除沒必要要的軟件包安裝(包括一些debug工具)。
其次,相似apt-get之類的系統包管理工具會產生緩存數據,咱們也應該清除。
Tip-6 : 在使用系統包管理工具安裝軟件包後清理緩存數據。
另外咱們應該使用Docker提供的多階段構建特性來減少最終的鏡像大小,咱們在後面介紹。
咱們進一步改進Dockerfile:
FROM ubuntu:18.04 RUN apt-get update \ && apt-get y install –no-install-recommends openjdk-8-jdk \ && rm -rf /var/lib/apt/lists/* COPY target/app.jar /app CMD [「java」,」-jar」,」/app/app.jar」]
咱們看看上面的Dockerfile,使用了一個ubuntu
的鏡像來安裝jdk包,而在安裝jdk包的不一樣時間點,可能會致使不一樣的版本,這樣就致使了鏡像的不易維護。
Tip-7 : 儘量使用應用(語言)運行時的官方基礎鏡像,並指定Tag版本
通常來講,官方會維護一些變種鏡像來提供多樣性,例如基於 alpine的,還有 -slim 精簡版本的,其次對於像Java應用,最終咱們須要的應該只是JRE,所以應該選擇jre的鏡像,這樣既保證了可維護性,同時也能夠減少鏡像的大小。
FROM openjdk:8-jre-alpine COPY target/app.jar /app CMD [「java」,」-jar」,」/app/app.jar」]
咱們應該保障咱們的鏡像構建在任什麼時候候,以及任何構建服務器上是一致的,可是咱們看上面的Dockerfile,是將jar文件COPY到容器中,可是這個jar文件是在什麼環境構建的呢?
Tip-8 : 在一致的環境中從源代碼構建
一樣,Docker的多階段構建提供了最好的解決方案,將源碼編譯構建放到構建階段,將最終生成的軟件包COPY放到運行是階段。
Tip-9 : 使用多階段構建
FROM maven:3.6-jdk-11 AS builder WORKDIR /app COPY pom.xml . RUN mvn -e -B dependency:resolve COPY src ./src RUN mvn -e -B package FROM openjdk:11-jre-slim COPY –from=builder /app/target/app.jar / CMD [「java」,」-jar」,」/app.jar」]
例如上面的Dockerfile,咱們使用了maven
的鏡像來構建代碼,使用openjdk:jre
的鏡像來運行。
最後咱們來看看安全性,如何使咱們的應用容器更加安全。首先,容器裏包含的軟件包越少,那可能的漏洞就會越少,因此這也是 Tip-5 所強調的。
Tip-10 : 使用非root用戶運行容器應用進程
其次,咱們應該使用非root用戶來運行咱們的應用,默認狀況下容器都是使用root用戶來執行,咱們可使用如下兩種方法來使用非root用戶來運行。
USER
指令,記得在使用USER
指令前建立相應的用戶CMD
或者ENTRYPOINT
中使用su-exec
, gosu
等工具來啓動應用我推薦使用第二種方法,由於第一種方式,在啓動容器後進入容器會默認使用非root用戶,這樣不便於安裝某些調試工具來執行調試(固然也能夠經過配置sudo)。
而第二種方式須要安裝su-exec
等工具,我建議本身基於官方的基礎鏡像維護一些本身的運行時基礎鏡像,這樣避免在每次構建應用鏡像的時候都進行一次安裝。
FROM gradle:6.4-jdk11 as builder WORKDIR /code COPY . . RUN gradle assemble FROM mengzyou/openjdk:11-jre-alpine ENV APP_HOME="/opt/app" \ APP_USER="appuser" \ JAR_OPTS="--spring.profiles.active=prod" RUN addgroup ${APP_USER} && \ adduser -D -h ${APP_HOME} -S -G ${APP_USER} ${APP_USER} COPY --from=builder --chown=${APP_USER}:${APP_USER} /code/build/libs/*.jar ${APP_HOME}/app.jar EXPOSE 8080/tcp WORKDIR ${APP_HOME} CMD su-exec app java ${JAVA_OPTS} -jar ${APP_HOME}/app.jar ${JAR_OPTS} # CMD [「su-exec」,」appuser」,」sh -c」,」java -jar /opt/app/app.jar"]
在來一個golang的示例
FROM golang:1.14-alpine AS builder RUN apk add --no-cache git && \ mkdir -p $GOPATH/src/app \ WORKDIR $GOPATH/src/app COPY . $GOPATH/src/app RUN go mod tidy \ && go build -o /go/bin/app FROM mengzyou/alpine:3.12 ENV APP_HOME=/opt/app \ APP_USER=appuser RUN addgroup ${APP_USER} && \ adduser -D -h ${APP_HOME} -S -G ${APP_USER} ${APP_USER} COPY --from=builder --chown=${APP_USER}:${APP_USER} /go/bin/app ${APP_HOME}/ EXPOSE 8080/tcp WORKDIR ${APP_HOME} CMD su-exec ${APP_USER} ${APP_HOME}/app
這裏僅僅是在Dockerfile實踐中的一些提示,要寫好Dockerfile,還有不少方面須要注意的地方,可參考Docker官方的Best practices for writing Dockerfiles。