天天看點

【docker】 docker是怎麼運作容器服務的

我們這裡使用zookeeper來舉例子

傳統的zookeeper部署方式就是下載下傳zookeeper的jar包,部署好zookeeper所依賴的環境,然後通過運作jar包裡面的zkServer.sh腳本來啟動運作zookeeper服務

【docker】 docker是怎麼運作容器服務的

那麼現在比較流行的docker容器是怎麼啟動zookeeper服務的呢

Docker-compose部署單主機zookeeper叢集 ,我使用docker-compose 搭建了zookeeper叢集,我拉取的鏡像是zookeeper3.7.0官方鏡像。是以首先我們去 ​​docker hub​​ 然後在搜尋框裡面搜尋 zookeeper,我們這裡是拉取的官方的zookeeper鏡像,是以點選進入zookeeper官方image,看到如下介紹頁面

【docker】 docker是怎麼運作容器服務的

然後點選上圖連結去github,因為我們拉取的是zookeeper 3.7.0,是以點選進入

【docker】 docker是怎麼運作容器服務的

看到有建構鏡像的dockerfile檔案和一個docker-entrypoint.sh腳本檔案

【docker】 docker是怎麼運作容器服務的

Dockerfile檔案

我們可以看到如下的鏡像建構步驟

  1. FROM: 從jdk-11的基礎鏡像開始建構
  2. ENV:然後設定了一系列環境變量(會存在于建構好的容器裡面)
  3. RUN:然後添加使用者組(zookeeper),添加使用者(zookeeper),建立檔案夾,設定使用者組,使用者在這些檔案夾的權限
  4. RUN:安裝一些必要的軟體包
  5. ARG,建構參數,與 ENV(設定環境變量) 作用一緻。不過作用域不一樣。ARG 設定的環境變量僅對 Dockerfile内有效,也就是說隻有 docker build 的過程中有效,建構好的鏡像内不存在此環境變量。
  6. RUN:下載下傳zookeeper以及一些列環境配置
  7. WORKDIR:指定工作目錄。為 RUN、CMD、ENTRYPOINT、COPY 和 ADD 設定工作目錄,就是切換目錄
  8. VOLUME:定義匿名資料卷。在啟動容器時忘記挂載資料卷,會自動挂載到匿名卷。

    作用:避免重要的資料,因容器重新開機而丢失,這是非常緻命的。避免容器不斷變大。

  9. EXPOSE:僅僅隻是聲明端口。

    作用:幫助鏡像使用者了解這個鏡像服務的守護端口,以友善配置映射。

    在運作時使用随機端口映射時,也就是 docker run -P 時,會自動随機映射 EXPOSE 的端口。

  10. ENV:設定環境變量
  11. COPY:拷貝檔案或目錄到容器中,跟ADD指令類似,但不具備自動下載下傳或解壓的功能
  12. ENTRYPOINT:運作容器時執行的shell指令 -----docker-entrypoint.sh

    類似于 CMD 指令,但其不會被 docker run 的指令行參數指定的指令所覆寫,而且這些指令行參數會被當作參數送給 ENTRYPOINT 指令指定的程式。

    但是, 如果運作 docker run 時使用了 --entrypoint 選項,将覆寫 CMD 指令指定的程式。

    優點:在執行 docker run 的時候可以指定 ENTRYPOINT 運作所需的參數。

    注意:如果 Dockerfile 中如果存在多個 ENTRYPOINT 指令,僅最後一個生效。

  13. 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