更新於 2020-07-11。html
所謂的「最小」 tomcat 鏡像是相對的,它的大小取決於以下幾點:java
上述的條件決定了 tomcat 鏡像的大小。python
總所周知,alpine 算是基礎鏡像中最小的了,它還自帶包管理器,可是它的缺點也一樣明顯,就是它沒有使用 glibc,所以會帶來兼容性的問題。linux
本文會使用基於 glibc 的 distroless(基於 debian)做爲基礎鏡像,而後使用 oracle jdk8 + tomcat8,最終鏡像大小爲 181M(能夠更小點,可是會不實用)。這個大小是有必定浮動空間的,由於即便是使用 oracle jdk8,不一樣的小版本之間大小也會相差很大。git
如下是和官方鏡像的一個對比:github
鏡像名 | java | 大小 | 鏡像層 | glibc |
---|---|---|---|---|
tomcat:8 | openjdk jre | 463MB | 31 | glibc |
tomcat:8-alpine | openjdk jre | 106MB | 25 | muslc |
tomcat:8-slim | openjdk jre | 223MB | 29 | glibc |
本文編譯使用 | oracle jdk | 175M | 3 | glibc |
能夠看出 alpine 的優點很是大,它足夠小,可是它使用的是 jre,且不是 glibc。若是你程序編譯是針對 glibc,那麼運行起來會有問題。我也不肯定我公司的開發是否對 glibc 有強依賴,可是不敢冒險,並且 alpine 對我來講並無什麼優點。web
你能夠看到本文編譯使用的 tomcat 的鏡像層只有 3 層,你可能會吃驚於它的層數,等你看完你就會明白了,由於這個鏡像並非經過 dockerfile 構建出來的。docker
由於我已經將編譯好的鏡像上傳到了 dockerhub,你能夠直接使用 docker run maxadd/tomcat:8-jdk8-distroless
運行查看,或者使用 dive 查看其構成。shell
當你選擇 tomcat 鏡像時,其實要考慮不少東西:apache
真當你須要用的時候,發現官方鏡像使用起來或多或少都有些不順手,總不是那麼使人滿意。對我而言,最重要的是官方沒有 oracle jdk 的鏡像提供,由於狗日的 oracle 要對 oracle jdk 收費。雖然也有人本身提供了基於 oracle jdk 的版本,可是鏡像實在太大。
總之基於這樣或那樣的緣由,我準備手動建立一個本身的 tomcat 鏡像,這讓我將目光移向了 distroless 鏡像。由於 distroless 鏡像是全部基於 glibc 中最小的,只有 19M,裏面只包含一個二進制程序應有的最基礎的運行環境,沒有一個命令提供,包括 shell。
因爲 distroless 鏡像被牆,所以我已經將其上傳到 dockerhub,名稱爲
maxadd/distroless_base-debian10
。這種鏡像基本沒有動手腳的可能,你能夠直接拿來使用。
由於一開始我並不知道 dockerfile 中的 ADD 命令能夠直接解壓 tar 包,所以我還特意學習了構建 distroless 的 bazel 工具,所以本文會提到基於 bazel 的實現。你不會 bazel 也沒有關係,使用 ADD 就好。
使用 bazel 構建鏡像和基於 dockerfile 會有些一些的差異,使用 bazel 只會產生一個鏡像層,而無論你作了多少操做;而使用 dockerfile,你每執行一個 dockerfile 命令都會產生一個鏡像層,包括 ENV
這類的命令。而且在 dive 命令的視角下,兩者構建的鏡像也會有所差異。
其實主要是兩者的理念不一樣,bazel 是直接一次性將鏡像構建完成,提供容器運行所需的全部文件;而 dockerfile 使用的是鏡像層的概念,每執行一個指令就會在其上增長一層。若是爲了這點差別去學 bazel 並不划算,建議直接使用 dockefile。不過兩種方式下面都會提到。
有了這樣的前提以後,我開始規劃個人鏡像:
下面一步步實現上面的需求。
個人作法是創建一個目錄,做爲根目錄,裏面存放須要複製到 distroless 鏡像中的全部文件。文件須要放在哪,那就在該目錄下建立對應的目錄。最終將這個「根目錄」打包,並在 distroless 的根目錄解壓,就能將全部文件一步到位。這也是鏡像層只有 3 層的緣由,其中 2 層是 distroless 自帶的。
第一步是安裝 bash,安裝 bash 的目的是爲了執行腳本,一樣也是爲了可以登陸上去執行 jmap、jstack 之類的命令。由於咱們後面會使用 busybox,busybox 會提供 sh,所以要不要安裝 bash,你看着辦。示例鏡像中沒有這個。
前面已經提到了,我這裏使用 distroless 做爲基礎鏡像,且由於 distroless 使用的是 debian 的庫文件,所以咱們能夠將 debian 中的命令直接複製下來使用。
Linux 中命令的運行不只須要命令自己,還須要它依賴的庫文件,庫文件經過 ldd
命令查看。所以咱們不只須要複製命令自己,還得複製它所需的庫文件。
因爲本次的 distroless 鏡像版本爲 gcr.io/distroless/base-debian10
,因此咱們首先須要啓動一個 debian 容器。雖然鏡像名稱中顯示使用的是 debian10,可是我發現將 debian10 的 bash 移植到 distroless 存在問題,而使用 debian9 的就沒有問題,所以這裏會使用 debian9 的 bash。
# docker run -it --name debian debian:9 /bin/bash 複製代碼
查看 bash 所依賴的庫文件:
root@45104fade344:/# ldd /bin/bash linux-vdso.so.1 (0x00007ffda05b1000) # 這一行無需理會 libtinfo.so.5 => /lib/x86_64-linux-gnu/libtinfo.so.5 (0x00007fdf4532f000) libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fdf4512b000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fdf44d8c000) /lib64/ld-linux-x86-64.so.2 (0x0000564175f7f000) 複製代碼
上面總共有四個庫文件,可是須要的只是 /lib/x86_64-linux-gnu/libtinfo.so.5
,其餘庫文件 distroless 中已經存在了。
咱們先在宿主機上建立所謂的「根目錄」,這裏取名爲 tomcat_jdk8。而後在這個目錄下建立 lib/x86_64-linux-gnu
、bin
、etc
、usr/local
、usr/lib/locale
這幾個目錄。
mkdir -p tomcat_jdk8/{lib/x86_64-linux-gnu,bin,etc,usr/{lib/locale,local}} 複製代碼
接着使用 docker cp 命令將 debian 容器中的命令和庫文件複製到咱們建立的對應的目錄下。
docker cp debian:/bin/bash tomcat_jdk8/bin/ # -L 表示複製連接文件指向的實際文件 docker cp -L debian:/lib/x86_64-linux-gnu/libtinfo.so.5 tomcat_jdk8/lib/x86_64-linux-gnu/ 複製代碼
經過這種方式能夠將你想要使用的其餘命令都拷貝下來,這裏就不一一演示了。
由於這裏我打算使用 oracle jdk 而非 openjdk,因此先從 oracle 官網上將 jdk8 下載下來。我這裏下載的是 162 版本,沒有別的緣由,只是由於公司使用的是這個版本。
注意下載 tar 包並解壓,我這裏將 java 解壓到了 /usr/local 目錄下:
# ls tomcat_jdk8/usr/local jdk1.8.0_162 複製代碼
完整的 jdk 總共有 371M,裏面有不少咱們用不到的東西,先將其都刪除掉:
# cd tomcat_jdk8/usr/local/jdk1.8.0_162 # rm -rf *src.zip \ lib/missioncontrol \ lib/visualvm \ lib/*javafx* \ jre/lib/plugin.jar \ jre/lib/ext/jfxrt.jar \ jre/bin/javaws \ jre/lib/javaws.jar \ jre/lib/desktop \ jre/plugin \ jre/lib/deploy* \ jre/lib/*javafx* \ jre/lib/*jfx* \ jre/lib/amd64/libdecora_sse.so \ jre/lib/amd64/libprism_*.so \ jre/lib/amd64/libfxplugins.so \ jre/lib/amd64/libglass.so \ jre/lib/amd64/libgstreamer-lite.so \ jre/lib/amd64/libjavafx*.so \ jre/lib/amd64/libjfx*.so 複製代碼
刪除以後,只剩 153M😆。而後給 jdk 目錄創建一個軟連接:
# ln -s jdk1.8.0_162 java 複製代碼
jdk 的 bin 目錄下有不少咱們須要的命令,這些命令也有依賴的庫文件。雖然 jdk 是咱們下載而並非咱們安裝,可是因爲 jdk 中的命令都是編譯好的二進制文件,只要知足內核和 glibc 的需求它們就能夠運行。
固然,內核和 glibc 只是硬性要求,軟性要求就是它們依賴的庫文件。因爲個人宿主機 Linux 的 CentOS7 而非 debian,所以咱們須要將 jdk 掛載到 debian 鏡像中,在鏡像中使用 ldd 命令來查看 jdk 中命令所依賴的庫文件有哪些。
我都查看了一番,結果發現 distroless 鏡像中庫文件徹底可以知足 jdk 全部命令的運行須要,因此不須要另外的文件。固然更新版本的 jdk 中可能存在其餘依賴的狀況,你能夠經過 dive 命令查看 distroless 鏡像中存在哪些庫文件,而後對比命令依賴的庫文件,若是 distroless 中不存在,那你就要提供了。
OK,接下來就是準備 tomcat 了。
tomcat 裏面沒有任何命令,它自己也是依賴 java 啓動的,所以它沒有任何的庫文件依賴。直接去官網下一個就行,我這裏使用的是 apache-tomcat-8.5.56
。
我這裏一樣將之放在 usr/local 目錄下:
# ls tomcat_jdk8/usr/local/ apache-tomcat-8.5.56 java jdk1.8.0_162 複製代碼
tomcat 的啓動咱們是經過 catalina.sh 進行的,可是因爲它裏面用到的命令太多,且咱們只須要啓動 tomcat,不須要中止或者重啓之類的,因此咱們徹底能夠不用 catalina.sh,只須要它啓動所需的 java 參數就行。拿到這些參數以後,咱們直接傳遞給 java 後一樣能夠直接啓動。
這個參數其實很好得到,你將 tomcat 和 jdk 同時映射到 debian 容器中,而後定義好 JAVA_HOME
,就能夠經過 sh -x catalina.sh run
看到它最終啓動的參數了。甚至我懷疑只要在 Linux 服務器上直接執行就行,不必掛載到 debian 中。
我這裏就不演示具體的操做了,直接將它的參數貼出來。當你什麼 JAVA 參數都沒有設定時,它的啓動參數以下:
/usr/local/java/bin/java -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djdk.tls.ephemeralDHKeySize=2048 -Djava.endorsed.dirs=/usr/local/tomcat/endorsed -classpath /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar -Dcatalina.base=/usr/local/tomcat -Dcatalina.home=/usr/local/tomcat -Djava.io.tmpdir=/usr/local/tomcat/temp org.apache.catalina.startup.Bootstrap start 複製代碼
你若是想要增長 jvm 參數,隨便往裏面插就行,你定義在 catalina.sh 中的相似於 JAVA_OPS
等,最終都會轉換成 java 參數。不過,貌似 jvm 中堆的參數以前要加上 -server
。
-server -Xmx128M -Xms128M
複製代碼
咱們但願經過一個環境變量來控制 tomcat jvm 的堆大小,所以咱們須要經過一個腳原本啓動 tomcat:
#!/bin/sh /usr/local/java/bin/java -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -server ${JAVA_OPTS} -Djava.endorsed.dirs=/usr/local/tomcat/endorsed -classpath /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar -Dcatalina.base=/usr/local/tomcat -Dcatalina.home=/usr/local/tomcat -Djava.io.tmpdir=/usr/local/tomcat/temp -Djdk.tls.ephemeralDHKeySize=2048 org.apache.catalina.startup.Bootstrap start 複製代碼
不僅是堆大小,任何你須要的參數均可以定義在 JAVA_OPTS
中,可能形成的結果就是這個變量會很是長。這個腳本你隨便放在哪,只要 CMD 指定執行它就行,這裏放在了 /usr/local/
目錄下,腳本名爲 start.sh。
再給 tomcat 創建一個軟鏈接,最後 usr/local 下面的內容爲:
ls tomcat_jdk8/usr/local/ apache-tomcat-8.5.56 config.yml java jdk1.8.0_162 tomcat 複製代碼
tomcat 監控主要手段仍是 jmx,tomcat 或者說 java 都支持 jmx。這裏的 jmx 監控經過 jmx_exporter 進行,這是配合 prometheus 使用的。它是一個 java agent,經過 -javaagent
參數啓動,關於 java agent 相關的內容這裏就很少提了,由於我知道的也很少😂。
下載完成後。我將其放在 usr/local/
目錄,而後在同一目錄下爲其提供一個名爲 config.yml
的配置文件:
--- startDelaySeconds: 0 ssl: false lowercaseOutputName: false lowercaseOutputLabelNames: false whitelistObjectNames: - "com.alibaba.druid:type=DruidDataSource,*" - "java.lang:*" 複製代碼
這裏給 jmx 收集的對象設置了一個白名單,這有對象名符合的才收集。若是你不知道 tomcat 都提供了有哪些對象,可使用 jconsole 鏈接 jmx 以後,查看 mbean 標籤下的內容,會列出當前 jmx 提供的全部對象。
由於咱們的開發使用了 alibaba 的 druid 鏈接池,因此這裏也收集了它的指標。固然,若是整個 jmx 的指標你都要收集的話,將白名單都去掉就好。可是我不建議你這麼作,由於指標太多。在示例的鏡像中,只保留了 java.lang:*
,並無收集 druid 鏈接池的內容。這裏只是示範若是你想要收集其餘指標的時候,應該怎麼作。
啓動 tomcat 默認並不會加載這個 java agnet,你須要將其添加到 JAVA_OPTS
這個環境變量中。啓動方式以下:
-javaagent:/usr/local/jmx_prometheus_javaagent-0.13.0.jar=12356:/usr/local/config.yml 複製代碼
後面會提到如何經過 docker 啓動。這樣啓動鏡像以後,你只要訪問 http://IP:12356/metrics
(容器 ip),就會打印出全部其收集的 jmx 指標。
busybox 是一個 Linux 命令,它可以以不到 1M 的大小來模擬 300+ 的 Linux 經常使用命令,具體底層的實現原理尚不明確,可是很是適用於對存儲空間有要求的場景。
咱們能夠在 debian10 容器中安裝 busybox,而後將其經過 docker cp 命令 copy 到 tomcat_jdk/bin 目錄下(依賴的庫文件 distroless 已經存在)。
它的用法很簡單,好比你要經過它來模擬 ls 命令,能夠這麼作:
./busybox ls /etc/
複製代碼
第二種使用方式是建立一個 ls 的軟連接文件,指向 busybox:
ln -s busybox ls ./ls /etc/ 複製代碼
這只是拿 ls 命令舉例,其餘的命令都是這種用法。那麼它支持模擬哪些命令呢?經過 busybox --list
能夠查看到。你能夠經過下面的方式爲其支持的全部命令建立軟連接:
# docker run --rm -it -v tomcat_jdk8/bin:/opt debian:10 /bin/bash root@e39f29c2648d:/# cd /opt/ root@e39f29c2648d:/opt# for i in $(./busybox --list);do ln -s busybox $i;done 複製代碼
這就至關於你的 tomcat 鏡像中存在 300+ 命令。
distroless 默認不支持中文,想讓它支持中文也很簡單,只須要在 debain10 容器中執行以下命令:
# docker run --rm -it debian:10 /bin/bash apt update apt install -y locales localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 複製代碼
命令執行後,會生成 /usr/lib/locale/locale-archive
文件,你只須要使用 docker cp 命令將這個文件 copy 出來,放置到 tomcat_jdk8/usr/lib/locale/locale-archive 便可。
時區默認爲 UTC,咱們只需將 debian10 容器中的 /usr/share/zoneinfo/Asia/Shanghai 文件放入到 tomcat_jdk8/etc/localtime 便可。
其實 docker 容器自己就是 Linux 上的一個進程,你運行起來以後是能夠在宿主機上經過 ps 命令看到的,而且能夠看到進程的運行用戶是 root。
有些運維常識的人都應該知道,除非萬不得已,進程不該該使用 root 啓動。那咱們是否是應該在容器中建立一個普通用戶,而後使用這個普通用戶來啓動容器呢?答案是不須要。
由於容器其實就是宿主機上的進程,咱們是能夠將其運行的用戶指定爲宿主機上的用戶的。docker 啓動容器時能夠經過 docker run -u
指定宿主機用戶名,而 k8s 中則能夠經過 pod.spec.containers.securityContext.runAsUser
來指定宿主機的 uid。這麼一來,你就能夠在運行容器的宿主機上經過 ps 命令看到容器運行的用戶爲你指定的用戶,而不管鏡像中是否存在該用戶。
因此,咱們無需在宿主機中建立普通用戶。
若是你已經決定好用使用普通用戶來運行 tomcat 容器,那麼你最好將 tomcat 的某些目錄(好比日誌目錄 tomcat_jdk8/usr/local/tomcat/logs)的屬主改成你準備運行 tomcat 的用戶,避免運行後普通用戶沒法寫入日誌。
示例 docker 鏡像中的 tomcat 目錄屬主屬組爲 root,若是你要用普通用戶運行這個鏡像,是沒法寫入日誌的。不過能夠經過掛載日誌目錄的方式進行,這個在後面的日誌收集的文章中會提到。
最終「根目錄」的文件有這些:
tomcat_jdk8/ ├── bin │ ├── bash │ ├── beep -> busybox │ ├── blkid -> busybox │ ├── ... ├── etc │ ├── localtime ├── lib │ └── x86_64-linux-gnu │ └── libtinfo.so.5 └── usr ├── lib │ └── locale │ └── locale-archive └── local ├── apache-tomcat-8.5.56 │ ... ├── config.yml ├── java -> jdk1.8.0_162 ├── jdk1.8.0_162 │ ... ├── jmx_prometheus_javaagent-0.13.0.jar └── tomcat -> apache-tomcat-8.5.56/ 複製代碼
確保文件都準備完畢後,咱們就能夠對該目錄(這裏是 tomcat_jdk8)進行打包了,打包的格式必須是 tar/tar.gz/tar.xz 等。
有一點須要注意,使用 tar 命令沒法完成這種操做(或許是我不知道方法?),由於 tar 必須在它上級目錄打包,可是這樣一來 tar 中就包含 tomcat_jdk8 這個目錄名了。這就形成在 distroless 中解壓后里面文件都不會直接放在根下,而是還在 tomcat_jdk8 下。
既然 tar 不行,那就使用 Python 進行打包。你不會 Python 沒關係,跟着我走就不會出任何問題。
首先啓動 python3 鏡像,注意將 debian_file 映射到 python3 的 /opt 目錄下(你若是本地有 python 環境的話就不必這麼麻煩)。
docker run -it -v /root/tomcat_jdk8:/opt python:3.6
複製代碼
而後執行下面這些代碼:
import tarfile, os os.chdir("/opt") tar = tarfile.open("/tmp/x.tar", "w") for i in os.listdir("."): tar.add(i) tar.close() 複製代碼
這會將 tomcat_jdk8 中的全部文件都打包到容器中的 /tmp/x.tar
文件中。而後使用 docker cp
將其複製到宿主機的 /tmp
下,留做後用。
ok,準備工做都已完成,中止 Python 容器後就能夠製做鏡像了,方式是將 /tmp/x.tar 解壓到 distroless 鏡像中。
咱們既能夠經過 dockerfile 來製做鏡像,又能夠經過 bazel。這裏先使用 dockerfile,由於它夠簡單。
新建一個目錄:
mkdir dockerfiles cd dockerfiles 複製代碼
將 /tmp/x.tar 複製到當前目錄下以後,新建 Dockerfile
文件:
FROM maxadd/distroless_base-debian10 ADD x.tar . ENV JAVA_HOME=/usr/local/java LANG="en_US.utf8" PATH="/bin:/usr/local/java/bin" EXPOSE 8080/tcp 12356/tcp CMD ["/usr/local/start.sh"] 複製代碼
將 /tmp/x.tar 複製到當前目錄下後,執行 docker build 命令:
docker build -t tomcat:8-jdk8-distroless .
複製代碼
bazel 是 Google 推出的編譯工具,用於將各類語言的源代碼編譯成二進制文件,至於有什麼優點我沒有具體瞭解 😜。從這點上來看,編譯 docker 鏡像只是它附帶的功能,事實也確實如此,它並不是原生支持 docker 鏡像的編譯。
使用 bazel 編譯 docker 鏡像的一大優點就是你甚至無需安裝 docker,不過真要使用方便,仍是得安裝。
有一個不幸的消息是,使用 bazel 過程當中須要*翻,所以沒有這種手段的童鞋就只能抱歉了。
首先安裝 bazel:
# cat >/etc/yum.repos.d/bazel.repo <<'EOF' [vbatts-bazel] name=Copr repo for bazel owned by vbatts baseurl=https://copr-be.cloud.fedoraproject.org/results/vbatts/bazel/epel-7-$basearch/ type=rpm-md skip_if_unavailable=True gpgcheck=1 EOF # yum install bazel -y 複製代碼
bazel 原生並不支持編譯 docker 鏡像,不過 GitHub 上面有擴展規則能夠幫你完成。
首先建立一個目錄,以它做爲 WORKSPACE:
# mkdir bazel 複製代碼
而後定義 WORKSPACE,也就是外部依賴。其實咱們依賴就是 docker 規則,所以加載它就好。
# cd bazel # vim WORKSPACE load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") # Download the rules_docker repository at release v0.14.3 http_archive( name = "io_bazel_rules_docker", sha256 = "6287241e033d247e9da5ff705dd6ef526bac39ae82f3d17de1b69f8cb313f9cd", strip_prefix = "rules_docker-0.14.3", urls = ["https://github.com/bazelbuild/rules_docker/releases/download/v0.14.3/rules_docker-v0.14.3.tar.gz"], ) # OPTIONAL: Call this to override the default docker toolchain configuration. # This call should be placed BEFORE the call to "container_repositories" below # to actually override the default toolchain configuration. # Note this is only required if you actually want to call # docker_toolchain_configure with a custom attr; please read the toolchains # docs in /toolchains/docker/ before blindly adding this to your WORKSPACE. # BEGIN OPTIONAL segment: load("@io_bazel_rules_docker//toolchains/docker:toolchain.bzl", docker_toolchain_configure="toolchain_configure" ) #docker_toolchain_configure( # name = "docker_config", # # OPTIONAL: Path to a directory which has a custom docker client config.json. # # See https://docs.docker.com/engine/reference/commandline/cli/#configuration-files # # for more details. # client_config="<enter absolute path to your docker config directory here>", # # OPTIONAL: Path to the docker binary. # # Should be set explcitly for remote execution. # docker_path="<enter absolute path to the docker binary (in the remote exec env) here>", # # OPTIONAL: Path to the gzip binary. # # Either gzip_path or gzip_target should be set explcitly for remote execution. # gzip_path="<enter absolute path to the gzip binary (in the remote exec env) here>", # # OPTIONAL: Bazel target for the gzip tool. # # Either gzip_path or gzip_target should be set explcitly for remote execution. # gzip_target="<enter absolute path (i.e., must start with repo name @...//:...) to an executable gzip target>", # # OPTIONAL: Path to the xz binary. # # Should be set explcitly for remote execution. # xz_path="<enter absolute path to the xz binary (in the remote exec env) here>", # # OPTIONAL: List of additional flags to pass to the docker command. # docker_flags = [ # "--tls", # "--log-level=info", # ], # #) # End of OPTIONAL segment. load( "@io_bazel_rules_docker//repositories:repositories.bzl", container_repositories = "repositories", ) container_repositories() # This is NOT needed when going through the language lang_image # "repositories" function(s). load("@io_bazel_rules_docker//repositories:deps.bzl", container_deps = "deps") container_deps() load( "@io_bazel_rules_docker//container:container.bzl", "container_pull", ) container_pull( name = "base", # 由於使用的是我上傳到 dockerhub 上的 distroless 鏡像,所以 registry 須要指定爲下面的值 registry = "registry-1.docker.io", repository = "maxadd/distroless_base-debian10", ) 複製代碼
因爲 bazel 的 docker 規則更新很頻繁,我這裏使用的是 v0.14.3。可能沒過多久以後它就行更新了,你還得去 github 上將它上面的最新規則給複製下來,否則你構建可能會失敗,由於 bazel build 的時候貌似會拉最新的規則。
定義好依賴以後,咱們在 WORKSPACE 所在目錄下建立一個空的 BUILD 文件(由於 build 過程當中會校驗這個文件),而後建立一個包,用來編譯鏡像。
# touch BUILD # mkdir tomcat # mv /tmp/x.tar . # 將 tar 包移動到當前目錄 # vim BUILD load( "@io_bazel_rules_docker//container:container.bzl", "container_image", ) container_image( name = "app", base = "@base//image", tars = ["x.tar"], env = { "PATH": "/bin:/usr/local/java/bin:/usr/local/java/jre/bin", "JAVA_HOME": "/usr/local/java", "LANG": "en_US.utf8" }, workdir = "/usr/local/tomcat/webapps", ports = ["8080", "12356"], cmd = ["/usr/local/tomcat/start.sh"] ) 複製代碼
開始編譯:
# cd .. # bazel build //tomcat:app 複製代碼
//tomcat:app
是一個 target,這是 bazel 的概念,使用它來指定咱們要編譯哪一個目錄。tomcat 指的是咱們建立的 tomcat 目錄,由於它下面有 BUILD 文件,因此它也稱爲一個包。app 則是指 BUILD 文件中 container_image
下面 name 的值,經過 //tomcat:app
就能定位到它的位置。
編譯很容易出錯,若是你不幸出錯了,只能本身想辦法解決了。編譯完成後,在當前目錄下執行:
bazel-bin/tomcat/app.executable
複製代碼
而後你可使用經過 docker images 看到 bazel/tomcat
這個鏡像了,注意它的 tag 是 app 而非 latest。
你能夠運行它,它和 dockefile 構建而成的鏡像沒有本質的區別,只有細微的不一樣。
無論經過哪一種方式構建鏡像,運行方式都是同樣的:
docker run --rm -it -e 'JAVA_OPTS=-Xmx200m -Xms200m -javaagent:/usr/local/jmx_prometheus_javaagent-0.13.0.jar=12356:/usr/local/config.yml' --cap-add=SYS_PTRACE -p 11111:12356 maxadd/tomcat:8-jdk8-distroless 複製代碼
之因此加上 --cap-add=SYS_PTRACE
是爲了可以使用 jps 等命令。
你甚至能夠經過 jmx 監控來查看它的性能指標:
curl 127.0.0.1:11111/metrics
複製代碼
OK,本篇文章也要結束了,其實使用 bazel 構建鏡像是有優點的,好比逼格很高。可是在國內使用起來仍是很困難的,包括沒有中文文檔,且使用過程當中要*牆,相比而言,使用它的性價比過低。
若是以前我知道 dockfile ADD 指令能夠解壓 tar 包,那麼我是不管如何都不會去了解它的,所以我對它的使用沒有過多的介紹。懂得天然懂,不懂直接使用 dockerfile 就好。