Flink docker 容器運行環境下不能夠從Web UI 查看 Logs 以及Stdout的解決辦法

背景

最近業務需要使用Flink, 於是把之前Flink的相關技術拿出來重新回顧一下, 於是想起這個之前一直沒有去解決的問題. 本文主要講解如何解決這一問題以及發生這個問題的根本原因.

運行Flink 官方docker image

此處不多說,訪問docker hub flink官方的Image. 選擇自己需要版本的flink官方鏡像(此處我選的是flink:scala_2.11 因爲要使用到scala shell所以選的scala版本不是最新的) 然後按照官方給的docker-compose 文件簡單改動一下啓動即可

version: "3"
services:
  jobmanager:
    image: flink:scala_2.11
    expose:
      - "6123"
    ports:
      - "8081:8081"
    command: jobmanager
    environment:
      - "JOB_MANAGER_RPC_ADDRESS=jobmanager"

  taskmanager:
    image: flink:scala_2.11
    expose:
      - "6121"
      - "6122"
    depends_on:
      - jobmanager
    command: taskmanager
    links:
      - "jobmanager:jobmanager"
    environment:
      - "JOB_MANAGER_RPC_ADDRESS=jobmanager"

此時訪問web ui localhost:8081 查看 jobmanager 的 logs 和 stdout
在這裏插入圖片描述
file unavailable 無法查看logs 和 stdout. 提交一個wordcount程序

object StreamingJob {
  def main(args: Array[String]) {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    val textStream = env.fromElements(
      "hello word",
            "this is flink demo"
    )

    val counts = textStream.flatMap { _.toLowerCase().split("\\W+") } .map { (_, 1) }.keyBy(0).sum(1)

    counts.print()
    env.execute("word count")
  }
}

查看結果, 運行結束且正常.
在這裏插入圖片描述
查看taskmanager的logs 和 stdout 依然是什麼都沒有, 此程序有print運行結果的,但是實際上並未查看到.
針對taskmanger容器 使用docker logs 查看日誌

docker logs 18a4a82ad9fe(taskmanager container id)

看到日誌裏面有print出來結果

Source: Collection Source -> Flat Map -> Map (1/1) 13fcdc91c88d72a6785c2b3d6f6e23c1.
(hello,1)
(word,1)
(this,1)
(is,1)
(flink,1)
(demo,1)

只是這個結果目前不能在web ui 的 taskmanager stdout上面查看.

解決方案

遇到此類配置相關的問題, 想必大家第一時間就是去stackoverflow上面去找答案. 上面確實有這個問題的答案,我把連接貼到這裏:Apache Flink: The file STDOUT is not available on the TaskExecutor

解決方案裏面更改了docker-entrypoint.sh 文件將jobmanager和taskmanager 的啓動相關參數, 下面是解決方案裏面的改動
在這裏插入圖片描述

下面是改動之前的腳本:

if [ "$1" = "help" ]; then
    echo "Usage: $(basename "$0") (jobmanager|${COMMAND_STANDALONE}|taskmanager|help)"
    exit 0
elif [ "$1" = "jobmanager" ]; then
    shift 1
    prepare_job_manager_start

    exec $(drop_privs_cmd) "$FLINK_HOME/bin/jobmanager.sh" start-foreground "$@"
elif [ "$1" = ${COMMAND_STANDALONE} ]; then
    shift 1
    prepare_job_manager_start

    exec $(drop_privs_cmd) "$FLINK_HOME/bin/standalone-job.sh" start-foreground "$@"
elif [ "$1" = "taskmanager" ]; then
    shift 1
    echo "Starting Task Manager"
    copy_plugins_if_required

    TASK_MANAGER_NUMBER_OF_TASK_SLOTS=${TASK_MANAGER_NUMBER_OF_TASK_SLOTS:-$(grep -c ^processor /proc/cpuinfo)}

    set_common_options
    set_config_option taskmanager.numberOfTaskSlots ${TASK_MANAGER_NUMBER_OF_TASK_SLOTS}

    if [ -n "${FLINK_PROPERTIES}" ]; then
        echo "${FLINK_PROPERTIES}" >> "${CONF_FILE}"
    fi
    envsubst < "${CONF_FILE}" > "${CONF_FILE}.tmp" && mv "${CONF_FILE}.tmp" "${CONF_FILE}"

    exec $(drop_privs_cmd) "$FLINK_HOME/bin/taskmanager.sh" start-foreground "$@"
fi

exec "$@"

對比一下兩者的區別, 改動前爲start-foreground 應該是前臺啓動的意思 改動之後是直接start 看樣子像是變成了後臺啓動. 然後在腳本最後執行了一個tail -f XXXX的命令 表示持續輸出某個文件的內容到console.
懂docker 容器技術的人看到這個tail -f XXX 應該瞬間明白了爲什麼作者要這樣處理. 容器中運行的程序至少一個前臺進程, 如果全是後臺進程,那麼容器一啓動就會立即停止. 所以當我們容器中的程序一定要以後臺進程方式運行的時候,一般容器的啓動腳本中都會加一個tail -f XXXXX保證有一個前臺程序一直在run, 這樣容器不會被關閉.

所以將docker-entrypoint.sh 執行jobmanager.sh 以及 執行taskmanager.sh的地方更改並在最後加上

exec /bin/bash -c "tail -f $FLINK_HOME/log/*.log"

保證容器中一直有前臺進程運行,改動後的docker-entrypoint.sh 部分如下:

if [ "$1" = "help" ]; then
    echo "Usage: $(basename "$0") (jobmanager|${COMMAND_STANDALONE}|taskmanager|help)"
    exit 0
elif [ "$1" = "jobmanager" ]; then
    shift 1
    prepare_job_manager_start

    $FLINK_HOME/bin/jobmanager.sh start "$@"
elif [ "$1" = ${COMMAND_STANDALONE} ]; then
    shift 1
    prepare_job_manager_start

    $FLINK_HOME/bin/standalone-job.sh start "$@"
elif [ "$1" = "taskmanager" ]; then
    shift 1
    echo "Starting Task Manager"
    copy_plugins_if_required

    TASK_MANAGER_NUMBER_OF_TASK_SLOTS=${TASK_MANAGER_NUMBER_OF_TASK_SLOTS:-$(grep -c ^processor /proc/cpuinfo)}

    set_common_options
    set_config_option taskmanager.numberOfTaskSlots ${TASK_MANAGER_NUMBER_OF_TASK_SLOTS}

    if [ -n "${FLINK_PROPERTIES}" ]; then
        echo "${FLINK_PROPERTIES}" >> "${CONF_FILE}"
    fi
    envsubst < "${CONF_FILE}" > "${CONF_FILE}.tmp" && mv "${CONF_FILE}.tmp" "${CONF_FILE}"

    $FLINK_HOME/bin/taskmanager.sh start "$@"
fi

sleep 1
exec /bin/bash -c "tail -f $FLINK_HOME/log/*.log"

用這個文件替換掉官方的docker-entrypoint.sh 構建自己的flink image 替換掉官方的image
運行docker-compose.yml 訪問localhost:8081查看jobmanager的log
在這裏插入圖片描述
發現通過web ui可以查看到日誌了.
同樣提交word count的程序查看stdout運行結果
在這裏插入圖片描述
至此,這個問題已經解決了.

問題根本原因

按照上面的步驟可以解決遇到的問題,但是需要找到問題的根本,是什麼原因導致官方的鏡像不能夠通過web ui查看log和 stdout, 只能夠通過docker logs {container_id}的方式去查看日誌和運行結果. 追本溯源還是 jobmanager.sh 和 taskmanager.sh 兩個腳本. 改前與改後的區別就是傳參不同, 一個是start-foreground 一個是start 查看jobmanager.sh源碼發現傳入這兩個參數之後真正執行的腳本是不同的

if [[ $STARTSTOP == "start-foreground" ]]; then
    exec "${FLINK_BIN_DIR}"/flink-console.sh $ENTRYPOINT "${args[@]}"
else
    "${FLINK_BIN_DIR}"/flink-daemon.sh $STARTSTOP $ENTRYPOINT "${args[@]}"
fi

官方鏡像傳入的start-foreground 後執行的腳本是flink-console.sh
而改動之後執行的是flink-daemon.sh. 其實看到這裏,大概就明白七八成了. 官方的方式運行的是flink-console.sh 即是命令行模式, 憑經驗命令行模式內容都打印在命令行裏, 屬於前臺執行進程. 而改動之後執行的是flink-daemnon.sh 表示flink將以後臺進程執行. 這也能解釋在改動的docker-entrypoint.sh 爲什麼最後會加一個tail -f XXXX的命令了. 因爲此處讓flink後臺執行了, 如果不加tail命令, 那麼容器中只有flink這個後臺進程,一啓動會立刻關閉.

在繼續深入下去看flink-console.sh 和 flink-daemon.sh 發現有一段日誌設置相關的代碼比較有意思:
flink-console.sh

log_setting=("-Dlog4j.configuration=file:${FLINK_CONF_DIR}/log4j-console.properties" "-Dlogback.configurationFile=file:${FLINK_CONF_DIR}/logback-console.xml")

以及 flink-daemon.sh

FLINK_LOG_PREFIX="${FLINK_LOG_DIR}/flink-${FLINK_IDENT_STRING}-${DAEMON}-${id}-${HOSTNAME}"
log="${FLINK_LOG_PREFIX}.log"
out="${FLINK_LOG_PREFIX}.out"

log_setting=("-Dlog.file=${log}" "-Dlog4j.configuration=file:${FLINK_CONF_DIR}/log4j.properties" "-Dlogback.configurationFile=file:${FLINK_CONF_DIR}/logback.xml")

這裏在flink-daemon.sh 中有一個 -Dlog.file的配置 log.file 非常關鍵的系統變量.而在flink-console.sh裏面沒有設置這個系統變量.
此時可以查看一下flink下conf裏面關於日誌的配置文件:
比如看log4j.properties

################################################################################
#  Licensed to the Apache Software Foundation (ASF) under one
#  or more contributor license agreements.  See the NOTICE file
#  distributed with this work for additional information
#  regarding copyright ownership.  The ASF licenses this file
#  to you under the Apache License, Version 2.0 (the
#  "License"); you may not use this file except in compliance
#  with the License.  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
# limitations under the License.
################################################################################

# This affects logging for both user code and Flink
log4j.rootLogger=INFO, file

# Uncomment this if you want to _only_ change Flink's logging
#log4j.logger.org.apache.flink=INFO

# The following lines keep the log level of common libraries/connectors on
# log level INFO. The root logger does not override this. You have to manually
# change the log levels here.
log4j.logger.akka=INFO
log4j.logger.org.apache.kafka=INFO
log4j.logger.org.apache.hadoop=INFO
log4j.logger.org.apache.zookeeper=INFO

# Log all infos in the given file
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.file=${log.file}
log4j.appender.file.append=false
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p %-60c %x - %m%n

# Suppress the irrelevant (wrong) warnings from the Netty channel handler
log4j.logger.org.apache.flink.shaded.akka.org.jboss.netty.channel.DefaultChannelPipeline=ERROR, file

發現裏面log4j.appender.file.file正好使用的是 log.file 這個系統變量.
所以這個問題的根源就是這個log.file系統變量配置問題. 在官方鏡像執行過程中, 由於運行的是flink-console.sh 腳本, 沒有設置這個log.file 因此不會再容器中{FLINK_HOME}/log 下面生成log以及stdout(可以自己啓動官方鏡像,然後進入容器去check,確實沒有log和out文件生成). 而web ui 正好是從這個目錄下獲取log和stdout. 改動之後使用flink-daemon.sh 啓動時, 會去設置這個log.file 系統變量, flink啓動後, log以及out都會存放在{FLINK_HOME}/log, 使用web ui 就能看到這些結果了. 其實當flink不以容器啓動的時候,都能夠看到log文件夾下面有log和out的文件, 官方鏡像啓動使用flink-console.sh 讓flink前臺啓動,保證容器啓動時有前臺進程,所以導致log和out沒有寫到log文件夾中, 進而也無法通過web ui 去查看log和stdout了.這就是這個問題的根本原因.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章