官方Tomcat鏡像
地址:https://hub.docker.com/_/tomcat/
鏡像的Full Description中,我們可以得到許多信息,這里簡單介紹幾個:
- Supported tags and respective Dockerfile links 支持的標簽,以及對應的Dockerfile鏈接。一個Dockerfile可以對應多個標簽,我們將以8.5.16-jre8版本的Dockerfile進行分析。
- How to use this image 如何使用本鏡像,包括了以不同方式運行容器的命令,以及一些主要環境變量。這里講的不夠詳細,我們會詳細講解。
准備知識:APR、Tomcat Native、Tomcat的APR模式
APR是Apache Portable Runtime的簡寫,其實就是一組用C語言編寫的動態鏈接庫,提供統一的API接口,用於訪問操作系統底層。詳見:http://apr.apache.org/。
Tomcat Native的主要作用是為tomcat提供訪問本地資源的功能。即利用APR庫,訪問網絡、生成隨機數等。Tomcat Native依賴於APR、OpenSSL、JDK。詳見:http://tomcat.apache.org/native-doc/。
在配置好Tomcat Native后,可以在tomcat配置文件中開啟APR模式。開啟之后,Tomcat就會直接調用APR庫訪問網絡,不用再通過jvm中轉。
因為APR和Tomcat Native是和具體的操作系統緊密關聯的,並不像java應用那樣編譯好后就可以跨平台使用。所以tomcat默認不安裝這些功能,而是根據需要,以及具體操作系統,手工編譯源碼安裝。從一些資料上來看,開啟tomcat的APR模式后,對tomcat的性能並不一定會有很大的提升(在某些情況下甚至可能下降)。是否開啟APR模式要看具體的情況。個人認為,一般情況下,真到了要挖掘Tomcat性能的時候,使用tomcat集群將會是一種更好的解決方案,這對系統的性能、穩定性、可靠性都有提升。
分析Dockerfile(tomcat:8.5.16-jre8)
地址:https://github.com/docker-library/tomcat/blob/master/8.5/jre8/Dockerfile 注意,這里以master分支的Dockerfile鏈接為例,和DockerHub上的Dockerfile鏈接可能會不一致,不過不影響我們接下來的分析。以下就是這個Dockerfile的內容,我加了注釋加以說明:
#本鏡像的基礎鏡像。有興趣的話,可以自行在DockerHub上搜索openjdk,分析官方的openjdk鏡像的Dockerfile文件。這里為什么不用oracle提供的jdk(jre)?簡單地講,版權問題。
FROM openjdk:8-jre
#聲明CATALINA_HOME環境變量,這個變量大家都了解。
ENV CATALINA_HOME /usr/local/tomcat
#將Tomcat下的bin路徑加入到PATH環境變量中。
ENV PATH $CATALINA_HOME/bin:$PATH
#創建tomcat路徑。
RUN mkdir -p "$CATALINA_HOME"
#指定RUN、CMD、ENTRYPOINT命令的當前工作路徑。
WORKDIR $CATALINA_HOME
#Tomcat Native路徑配置。
ENV TOMCAT_NATIVE_LIBDIR $CATALINA_HOME/native-jni-lib
#將TOMCAT_NATIVE_LIBDIR加入到LD_LIBRARY_PATH環境變量中,這樣Tomcat在查找Tomcat Native相關的動態鏈接庫時,會去查找TOMCAT_NATIVE_LIBDIR環境變量指定的路徑。
ENV LD_LIBRARY_PATH ${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$TOMCAT_NATIVE_LIBDIR
#檢查、更新OpenSSL,這塊的細節我沒深究。
ENV OPENSSL_VERSION 1.1.0f-3
RUN { \
echo 'deb http://deb.debian.org/debian stretch main'; \
} > /etc/apt/sources.list.d/stretch.list \
&& { \
echo 'Package: *'; \
echo 'Pin: release n=stretch'; \
echo 'Pin-Priority: -10'; \
echo; \
echo 'Package: openssl libssl*'; \
echo "Pin: version $OPENSSL_VERSION"; \
echo 'Pin-Priority: 990'; \
} > /etc/apt/preferences.d/stretch-openssl
RUN apt-get update && apt-get install -y --no-install-recommends \
libapr1 \
openssl="$OPENSSL_VERSION" \
&& rm -rf /var/lib/apt/lists/*
#從key服務器導入key,用於驗證tomcat壓縮文件的簽名,這塊也沒深究。
ENV GPG_KEYS 05AB33110949707C93A279E3D3EFE6B686867BA6 07E48665A34DCAFAE522E5E6266191C37C037D42 47309207D818FFD8DCD3F83F1931D684307A10A5 541FBE7D8F78B25E055DDEE13C370389288584E7 61B832AC2F1C5A90F0F9B00A1C506407564C17A3 713DA88BE50911535FE716F5208B0AB1D63011C7 79F7026C690BAA50B92CD8B66A3AD3F4F22C4FED 9BA44C2621385CB966EBA586F72C284D731FABEE A27677289986DB50844682F8ACB77FC2E86E29AC A9C5DF4D22E99998D9875A5110C01C5A2F6059E7 DCFD35E0BF8CA7344752DE8B6FB21E8933C60243 F3A04C595DB5B6A5F1ECA43E3B7BBB100D811BBE F7DA48BB64BCB84ECBA7EE6935CD23C10D498E23
RUN set -ex; \
for key in $GPG_KEYS; do \
gpg --keyserver ha.pool.sks-keyservers.net --recv-keys "$key"; \
done
#Tomcat相關文件的版本。
ENV TOMCAT_MAJOR 8
ENV TOMCAT_VERSION 8.5.16
#Tomcat相關文件下載地址。
ENV TOMCAT_TGZ_URL https://www.apache.org/dyn/closer.cgi?action=download&filename=tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz
ENV TOMCAT_ASC_URL https://www.apache.org/dist/tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz.asc
#執行命令
RUN set -x \
\
#下載Tomcat壓縮文件
&& wget -O tomcat.tar.gz "$TOMCAT_TGZ_URL" \
&& wget -O tomcat.tar.gz.asc "$TOMCAT_ASC_URL" \
#進行簽名驗證
&& gpg --batch --verify tomcat.tar.gz.asc tomcat.tar.gz \
#解壓Tomcat
&& tar -xvf tomcat.tar.gz --strip-components=1 \
# 刪除供Windows系統使用的.bat文件
&& rm bin/*.bat \
# 刪除壓縮文件
&& rm tomcat.tar.gz* \
\
#安裝Tomcat Native
&& nativeBuildDir="$(mktemp -d)" \
&& tar -xvf bin/tomcat-native.tar.gz -C "$nativeBuildDir" --strip-components=1 \
&& nativeBuildDeps=" \
dpkg-dev \
gcc \
libapr1-dev \
libssl-dev \
make \
openjdk-${JAVA_VERSION%%[-~bu]*}-jdk=$JAVA_DEBIAN_VERSION \
" \
&& apt-get update && apt-get install -y --no-install-recommends $nativeBuildDeps && rm -rf /var/lib/apt/lists/* \
&& ( \
export CATALINA_HOME="$PWD" \
&& cd "$nativeBuildDir/native" \
&& gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" \
&& ./configure \
--build="$gnuArch" \
--libdir="$TOMCAT_NATIVE_LIBDIR" \
--prefix="$CATALINA_HOME" \
--with-apr="$(which apr-1-config)" \
--with-java-home="$(docker-java-home)" \
--with-ssl=yes \
&& make -j "$(nproc)" \
&& make install \
) \
&& apt-get purge -y --auto-remove $nativeBuildDeps \
&& rm -rf "$nativeBuildDir" \
&& rm bin/tomcat-native.tar.gz
#驗證Tomcat Native是否安裝成功
RUN set -e \
&& nativeLines="$(catalina.sh configtest 2>&1)" \
&& nativeLines="$(echo "$nativeLines" | grep 'Apache Tomcat Native')" \
&& nativeLines="$(echo "$nativeLines" | sort -u)" \
&& if ! echo "$nativeLines" | grep 'INFO: Loaded APR based Apache Tomcat Native library' >&2; then \
echo >&2 "$nativeLines"; \
exit 1; \
fi
#暴露8080端口
EXPOSE 8080
#容器啟動時執行的命令。
CMD ["catalina.sh", "run"]
此Dockerfile的主要功能可歸納為:
- 以openjdk鏡像為基礎鏡像進行構建。
- 安裝Tomcat Native,及其依賴庫(比如APR、OpenSSL等)。驗證是否正確安裝。
- 下載Tomcat,檢查簽名,解壓、清除無用文件等。
- 暴露8080端口,配置入口命令。
總的來說,此Dockerfile的功能還是比較清晰的,如果要自己構建自己的Tomcat鏡像,可以參考本Dockerfile。另外,Tomcat的Dockerfile應該是自動生成而非手工編寫,因為第一,Tomcat的有很多版本的Dockerfile,手工維護工作量大;第二,存在一些我認為不是很很合理的地方(比如從key server上獲取很多key)。所以大家在分析一些官方Dockerfile時,如果遇到一些感覺不是很合理的地方也別太糾結。
如何使用官方鏡像
$ docker run -d --name tomcat-test -p 8888:8080 tomcat:8.5.16-jre8
以上指令將使用本鏡像啟動一個容器,你可以通過http://本機ip:8888訪問到容器中的Tomcat。這種方式並沒有部署應用,所以其實也沒什么實際意義。
$ docker run -d --name tomcat-test -p 8888:8080 \
-v /home/myWebApp:/usr/local/tomcat/webapps/ROOT \
-v /home/myWebAppLogs:/usr/local/tomcat/logs \
tomcat:8.5.16-jre8
以上指令增加了兩個volume,將/home/myWebApp掛載到容器中的/usr/local/tomcat/webapps/ROOT,將/home/myWebAppLogs掛載到容器中的/usr/local/tomcat/logs路徑。容器啟動后,tomcat的根應用將會是myWebApp(通過http://本機ip:8888訪問),並且tomcat的日志將會輸出到/home/myWebAppLogs路徑下。 此后,如果容器被刪除,應用和應用的日志也不會隨容器一起刪除。更新應用時,只需要更新/home/myWebApp路徑,再重啟容器即可。
如果不想把應用掛載到根目錄下,可以更改volume配置,比如:-v /home/myWebApp:/usr/local/tomcat/webapps/myWebApp,再通過http://本機ip:8888/myWebApp訪問。
注意:如果想把myWebApp內部的日志也輸出到/home/myWebAppLogs路徑下,請配置myWebApp的日志文件的輸出路徑為Tomcat日志路徑(相對路徑),例如:logs/myLog.log。這樣,myWebApp的日志文件將會輸出至/usr/local/tomcat/logs下(因為掛載了volume,其實是輸出到/home/myWebAppLogs路徑下)。
從規范的角度講,其實這種應用部署方式並不值得推薦,因為部署還是出現了環境相關性。也就是說,必須先把應用目錄、日志目錄指定好,才能進行部署。而且如果要一台機上要部署兩個相同的應用進行負載均衡也比較麻煩(要建兩套應用和日志目錄,並且修改容器啟動命令),難以實現自動化部署。但話又說回來,我相信這種部署方式還是會受到大部分人歡迎的。目前我們公司在測試環境中也普遍使用這種模式,以便一台測試服務器部署多個應用(Tomcat),彼此之間完全沒有關聯和影響。
如果要做到完全環境無關性部署,可以考慮自己在官方Tomcat鏡像的基礎上,構建出一個新的鏡像。在這個新鏡像的Dockerfile中直接下載並部署myWebApp,並使用某些工具或框架,將Tomcat和myWebApp產生的日志,上傳匯總到一個統一的日志服務中。這樣,如果要再部署一個相同的應用,只要給我一台裝有Docker環境的機器,我執行一條指令即可(同一Docker環境下注意端口沖突)。這種方式有利於自動化部署,在服務器數量較多的情況下比較合適。當然,這種方式也有比較麻煩的地方,就是每發布以一個應用版本,都要重新構建一個新的鏡像。
關於自動化部署,還可以參考 Docker Compose
和 Kubernetes
,這里不做深入(主要是本人對這塊的了解和應用也不夠多)。
官方鏡像的不足
在本鏡像的實際使用過程中,還是遇到了一些問題:
- 為了照顧到全球的鏡像使用者,官方的openjdk鏡像和Tomcat鏡像都沒對時區進行定制化配置,默認為UTC時間(比北京時間早八小時)。如果應用內部也沒有進行時區配置,那么應用獲取到的系統時間也將會是UTC時間。
- 在某些機器(或虛擬機)上,jdk的隨機數生成器初始化用時過長,導致Tomcat啟動用時過長。我用過的阿里雲ECS就是這種情況。
這些問題將會在之后我們自己構建的Tomcat鏡像中解決。