我們這裡使用zookeeper來舉例子
傳統的zookeeper部署方式就是下載下傳zookeeper的jar包,部署好zookeeper所依賴的環境,然後通過運作jar包裡面的zkServer.sh腳本來啟動運作zookeeper服務
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5iM5YDNzATNiNWZjZTY0ImNzYzX4IjNzQTM5AzLcFTMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
那麼現在比較流行的docker容器是怎麼啟動zookeeper服務的呢
Docker-compose部署單主機zookeeper叢集 ,我使用docker-compose 搭建了zookeeper叢集,我拉取的鏡像是zookeeper3.7.0官方鏡像。是以首先我們去 docker hub 然後在搜尋框裡面搜尋 zookeeper,我們這裡是拉取的官方的zookeeper鏡像,是以點選進入zookeeper官方image,看到如下介紹頁面
然後點選上圖連結去github,因為我們拉取的是zookeeper 3.7.0,是以點選進入
看到有建構鏡像的dockerfile檔案和一個docker-entrypoint.sh腳本檔案
Dockerfile檔案
我們可以看到如下的鏡像建構步驟
- FROM: 從jdk-11的基礎鏡像開始建構
- ENV:然後設定了一系列環境變量(會存在于建構好的容器裡面)
- RUN:然後添加使用者組(zookeeper),添加使用者(zookeeper),建立檔案夾,設定使用者組,使用者在這些檔案夾的權限
- RUN:安裝一些必要的軟體包
- ARG,建構參數,與 ENV(設定環境變量) 作用一緻。不過作用域不一樣。ARG 設定的環境變量僅對 Dockerfile内有效,也就是說隻有 docker build 的過程中有效,建構好的鏡像内不存在此環境變量。
- RUN:下載下傳zookeeper以及一些列環境配置
- WORKDIR:指定工作目錄。為 RUN、CMD、ENTRYPOINT、COPY 和 ADD 設定工作目錄,就是切換目錄
-
VOLUME:定義匿名資料卷。在啟動容器時忘記挂載資料卷,會自動挂載到匿名卷。
作用:避免重要的資料,因容器重新開機而丢失,這是非常緻命的。避免容器不斷變大。
-
EXPOSE:僅僅隻是聲明端口。
作用:幫助鏡像使用者了解這個鏡像服務的守護端口,以友善配置映射。
在運作時使用随機端口映射時,也就是 docker run -P 時,會自動随機映射 EXPOSE 的端口。
- ENV:設定環境變量
- COPY:拷貝檔案或目錄到容器中,跟ADD指令類似,但不具備自動下載下傳或解壓的功能
-
ENTRYPOINT:運作容器時執行的shell指令 -----docker-entrypoint.sh
類似于 CMD 指令,但其不會被 docker run 的指令行參數指定的指令所覆寫,而且這些指令行參數會被當作參數送給 ENTRYPOINT 指令指定的程式。
但是, 如果運作 docker run 時使用了 --entrypoint 選項,将覆寫 CMD 指令指定的程式。
優點:在執行 docker run 的時候可以指定 ENTRYPOINT 運作所需的參數。
注意:如果 Dockerfile 中如果存在多個 ENTRYPOINT 指令,僅最後一個生效。
-
CMD: zkServer.sh, start-foreground
類似于 RUN 指令,用于運作程式,但二者運作的時間點不同:
CMD 在docker run 時運作。
RUN 是在 docker build中運作
作用:為啟動的容器指定預設要運作的程式,程式運作結束,容器也就結束。CMD 指令指定的程式可被 docker run 指令行參數中指定要運作的程式所覆寫。
注意:如果 Dockerfile 中如果存在多個 CMD 指令,僅最後一個生效。
FROM openjdk:11-jre-slim
ENV ZOO_CONF_DIR=/conf \
ZOO_DATA_DIR=/data \
ZOO_DATA_LOG_DIR=/datalog \
ZOO_LOG_DIR=/logs \
ZOO_TICK_TIME=2000 \
ZOO_INIT_LIMIT=5 \
ZOO_SYNC_LIMIT=2 \
ZOO_AUTOPURGE_PURGEINTERVAL=0 \
ZOO_AUTOPURGE_SNAPRETAINCOUNT=3 \
ZOO_MAX_CLIENT_CNXNS=60 \
ZOO_STANDALONE_ENABLED=true \
ZOO_ADMINSERVER_ENABLED=true
# Add a user with an explicit UID/GID and create necessary directories
RUN set -eux; \
groupadd -r zookeeper --gid=1000; \
useradd -r -g zookeeper --uid=1000 zookeeper; \
mkdir -p "$ZOO_DATA_LOG_DIR" "$ZOO_DATA_DIR" "$ZOO_CONF_DIR" "$ZOO_LOG_DIR"; \
chown zookeeper:zookeeper "$ZOO_DATA_LOG_DIR" "$ZOO_DATA_DIR" "$ZOO_CONF_DIR" "$ZOO_LOG_DIR"
# Install required packges
RUN set -eux; \
apt-get update; \
DEBIAN_FRONTEND=noninteractive \
apt-get install -y --no-install-recommends \
ca-certificates \
dirmngr \
gosu \
gnupg \
netcat \
wget; \
rm -rf /var/lib/apt/lists/*; \
# Verify that gosu binary works
gosu nobody true
ARG GPG_KEY=AF3D175EC05DB249738D01AC8D8C3C3ED0B02E66
ARG SHORT_DISTRO_NAME=zookeeper-3.7.0
ARG DISTRO_NAME=apache-zookeeper-3.7.0-bin
# Download Apache Zookeeper, verify its PGP signature, untar and clean up
RUN set -eux; \
ddist() { \
local f="$1"; shift; \
local distFile="$1"; shift; \
local success=; \
local distUrl=; \
for distUrl in \
'https://www.apache.org/dyn/closer.cgi?action=download&filename=' \
https://www-us.apache.org/dist/ \
https://www.apache.org/dist/ \
https://archive.apache.org/dist/ \
; do \
if wget -q -O "$f" "$distUrl$distFile" && [ -s "$f" ]; then \
success=1; \
break; \
fi; \
done; \
[ -n "$success" ]; \
}; \
ddist "$DISTRO_NAME.tar.gz" "zookeeper/$SHORT_DISTRO_NAME/$DISTRO_NAME.tar.gz"; \
ddist "$DISTRO_NAME.tar.gz.asc" "zookeeper/$SHORT_DISTRO_NAME/$DISTRO_NAME.tar.gz.asc"; \
export GNUPGHOME="$(mktemp -d)"; \
gpg --keyserver ha.pool.sks-keyservers.net --recv-key "$GPG_KEY" || \
gpg --keyserver pgp.mit.edu --recv-keys "$GPG_KEY" || \
gpg --keyserver keyserver.pgp.com --recv-keys "$GPG_KEY"; \
gpg --batch --verify "$DISTRO_NAME.tar.gz.asc" "$DISTRO_NAME.tar.gz"; \
tar -zxf "$DISTRO_NAME.tar.gz"; \
mv "$DISTRO_NAME/conf/"* "$ZOO_CONF_DIR"; \
rm -rf "$GNUPGHOME" "$DISTRO_NAME.tar.gz" "$DISTRO_NAME.tar.gz.asc"; \
chown -R zookeeper:zookeeper "/$DISTRO_NAME"
WORKDIR $DISTRO_NAME
VOLUME ["$ZOO_DATA_DIR", "$ZOO_DATA_LOG_DIR", "$ZOO_LOG_DIR"]
EXPOSE 2181 2888 3888 8080
ENV PATH=$PATH:/$DISTRO_NAME/bin \
ZOOCFGDIR=$ZOO_CONF_DIR
COPY docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["zkServer.sh", "start-foreground"]
docker-entrypoint.sh
這是一個腳本檔案,在docker run執行的時候會被調用,主要細節可以看看下面這個
【docker】 dockerfile 的CMD ENTRYPOINT指令差別解析 這裡會執行如下代碼,第一個是腳本檔案,會放在容器裡面的根目錄下面
第二個和第三個是腳本檔案執行的時候,傳入的參數
/docker-entrypoint.sh zkServer.sh start-foreground
腳本檔案裡面寫了注釋:得知這個腳本主要是運作容器啟動指令和基本配置檔案的部署(zoo.cfg和myid等)
#!/bin/bash
set -e
# Allow the container to be started with `--user`
# $1 代表擷取腳本的第一個參數,因為我們在docker run的時候可以自己指定參數,就會覆寫掉CMD中的zhServer.sh 和 start-foreground
# shell判斷是否為root權限(id -u = 0) $@ 表示所有參數 $0表示腳本檔案
# 容器中不要使用root賬号,gosu是個工具,用來提升指定賬号的權限,作用與sudo指令類似,
#是以這裡是使用 dockerfile 建立的zookeeper賬戶來運作這個腳本檔案的
if [[ "$1" = 'zkServer.sh' && "$(id -u)" = '0' ]]; then
chown -R zookeeper "$ZOO_DATA_DIR" "$ZOO_DATA_LOG_DIR" "$ZOO_LOG_DIR"
exec gosu zookeeper "$0" "$@"
fi
# 如果zookeeper配置檔案不存在,這裡會幫我們建立一個配置檔案,并設定好如下配置内容
# Generate the config only if it doesn't exist
if [[ ! -f "$ZOO_CONF_DIR/zoo.cfg" ]]; then
CONFIG="$ZOO_CONF_DIR/zoo.cfg"
{
echo "dataDir=$ZOO_DATA_DIR"
echo "dataLogDir=$ZOO_DATA_LOG_DIR"
echo "tickTime=$ZOO_TICK_TIME"
echo "initLimit=$ZOO_INIT_LIMIT"
echo "syncLimit=$ZOO_SYNC_LIMIT"
echo "autopurge.snapRetainCount=$ZOO_AUTOPURGE_SNAPRETAINCOUNT"
echo "autopurge.purgeInterval=$ZOO_AUTOPURGE_PURGEINTERVAL"
echo "maxClientCnxns=$ZOO_MAX_CLIENT_CNXNS"
echo "standaloneEnabled=$ZOO_STANDALONE_ENABLED"
echo "admin.enableServer=$ZOO_ADMINSERVER_ENABLED"
} >> "$CONFIG"
# -z 如果該變量的字元串長度為0則為真
if [[ -z $ZOO_SERVERS ]]; then
ZOO_SERVERS="server.1=localhost:2888:3888;2181"
fi
for server in $ZOO_SERVERS; do
echo "$server" >> "$CONFIG"
done
if [[ -n $ZOO_4LW_COMMANDS_WHITELIST ]]; then
echo "4lw.commands.whitelist=$ZOO_4LW_COMMANDS_WHITELIST" >> "$CONFIG"
fi
for cfg_extra_entry in $ZOO_CFG_EXTRA; do
echo "$cfg_extra_entry" >> "$CONFIG"
done
fi
#配置zookeeper的myid檔案(如果沒找到myid檔案的話)
# Write myid only if it doesn't exist
if [[ ! -f "$ZOO_DATA_DIR/myid" ]]; then
echo "${ZOO_MY_ID:-1}" > "$ZOO_DATA_DIR/myid"
fi
exec "$@"
根據如上腳本可以得知,是使用的dockerfile 建立的zookeeper使用者來運作這個代碼的
gosu 指令運作的更多資訊可以參考 這篇: docker與gosu
腳本代碼中的真正運作容器的執行指令如下
exec gosu zookeeper "$0" "$@" == exec gosu zookeeper /docker-entrypoint.sh zhServer.sh start-foreground
gosu zookeeper “$ 0” “$@” 前面加上個exec,表示以gosu zookeeper " $ 0" “$@” 這個指令啟動的程序替換正在執行的docker-entrypoint.sh程序
總結:dockerfile建構好鏡像之後,docker run 運作這個鏡像的一個容器的時候(鏡像是類,容器是對象,這麼一個對比關系)
那麼就會執行如下代碼,來啟動容器
/docker-entrypoint.sh zhServer.sh start-foreground