在将所有日志打印到CloudWatch Logs之前,AWS-ECS上的Docker容器将退出

时间:2019-02-12 18:56:12

标签: amazon-web-services docker amazon-ecs docker-container amazon-cloudwatchlogs

为什么CloudWatch Logs Group的某些流不完整(即ECS任务的Docker Container成功退出,但日志突然停止更新)?几乎在所有日志组中都间歇性地看到这一点,但并不是在每个日志流/任务运行时都如此。

Dockerfile使用CMD命令运行node.js或Python脚本。

这些不是服务器/长时间运行的进程,我的用例要求容器在任务完成时退出。

示例Dockerfile:

FROM node:6
WORKDIR /path/to/app/
COPY package*.json ./
RUN npm install
COPY . .
CMD [ "node", "run-this-script.js" ]


使用docker run在本地终端上运行此命令时,所有日志均正确打印到我的终端的stdout / stderr。
要在Fargate上将其作为ECS任务运行,请从CloudFormation模板中将其日志驱动程序设置为awslogs

...
LogConfiguration:
   LogDriver: 'awslogs'
     Options:
        awslogs-group: !Sub '/ecs/ecs-task-tasks-${TaskName}'
        awslogs-region: !Ref AWS::Region
        awslogs-stream-prefix: ecs
...

看到有时cloduwatch日志输出不完整。我已经运行测试并检查了CW Logs Limits中的每个限制,并且确定问题不存在。
最初,我认为这是节点js在刷新console.log()之前异步退出的问题,或者该进程退出得太早了,但是通过添加睡眠计时器导致代码延迟并没有起作用。


此外,Python容器中也会出现相同的错误,这使我相信这不是代码问题,而是Cloudwatch的问题。
可能是由于docker容器在任务完成后立即退出,因此日志没有足够的时间写到CWLogs,但是必须有一种方法来确保不会发生这种情况?

样本日志: 流不完整:

{ "message": "configs to run", "data": {"dailyConfigs":"filename.json"]}}
running for filename

完整的日志流:

{ "message": "configs to run", "data": {"dailyConfigs":"filename.json"]}}
running for filename

stdout: entered query_script
... <more log lines>
stderr:
real 0m23.394s
user 0m0.008s
sys 0m0.004s
(node:1) DeprecationWarning: PG.end is deprecated - please see the upgrade guide at https://node-postgres.com/guides/upgrading

4 个答案:

答案 0 :(得分:2)

使用ECS Fargate容器运行Python脚本时,我已经看到了相同的行为-并且产生了同样的挫败感!

我认为这是由于CloudWatch Logs Agent批量发布日志事件所致:

  

如何批处理日志事件?

     

批处理已满,并且满足以下任一条件时将发布:

     
      
  1. 自添加第一个日志事件以来已过去buffer_duration的时间。

  2.   
  3. 已累积少于batch_size个日志事件,但添加新的日志事件超过了batch_size

  4.   
  5. 日志事件数已达到batch_count

  6.   
  7. 批处理中的日志事件跨度不超过24小时,但是添加新的日志事件超过了24小时约束。

  8.   
     

(参考:https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/AgentReference.html

因此,可能的解释是,日志事件由代理缓冲,但在ECS任务停止时尚未发布。 (如果是这样,这似乎是一个ECS问题-任何AWS ECS工程师是否愿意对此发表看法??)

似乎没有直接的方法可以确保日志已发布,但是它确实建议人们可以等待至少buffer_duration秒(默认为5秒),并且任何先前的日志都应该发布

我将在下面描述一些测试,这是我采用的解决方法。 Shell脚本run_then_wait.sh包装命令以触发Python脚本,并在脚本完成后添加睡眠。

Dockerfile

FROM python:3.7-alpine
ADD run_then_wait.sh .
ADD main.py .

# The original command
# ENTRYPOINT ["python", "main.py"]

# To run the original command and then wait
ENTRYPOINT ["sh", "run_then_wait.sh", "python", "main.py"]

run_then_wait.sh

#!/bin/sh
set -e

# Run the given command
"$@"
command_status="$?"

echo "Waiting for logs to flush to CloudWatch Logs..."
sleep 10  # twice the `buffer_duration` default of 5 seconds

exit "$command_status"

main.py

import logging
import time

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger()

if __name__ == "__main__":
    # After testing some random values, had most luck to induce the
    # issue by sleeping 9 seconds here; would occur ~30% of the time
    time.sleep(9)
    logger.info("Hello world")

希望该方法可以适应您的情况。您也可以在脚本中实现睡眠,但是要确保它不管终止如何发生都可能比较棘手。

很难证明建议的解释是正确的,因此我使用上面的代码测试了解决方法是否有效。测试是原始命令与run_then_wait.sh的对比,每次运行30次。结果是,分别在30%的时间和0%的时间观察到此问题。希望这对您同样有效!

答案 1 :(得分:1)

只需就此问题联系AWS支持,以下是他们的回复:

  

...

     

基于这种情况,我可以看到这种情况发生在   Fargate在输出到stdout / stderr后快速退出的任务。它   似乎与awslogs驱动程序的工作方式以及Docker in   Fargate与CW端点通信。

     

看看我们的内部票,我可以看到我们的   服务团队仍在为此寻求永久解决方案   报告的错误。不幸的是,修复程序没有共享的ETA   将被部署。但是,我借此机会添加了这个   将内部票箱告知类似情况并尝试   加快过程

     

同时,可以通过延长使用寿命来避免这种情况。   通过在记录之间添加延迟(〜> 10秒)来退出容器   应用程序的输出和流程的退出(退出   容器)。

     

...

答案 2 :(得分:0)

我也观察到了这一点。它一定是ECS错误吗?

我的解决方法(Python 3.7):

import atexit
from time import sleep

atexit.register(finalizer)

def finalizer():
    logger.info("All tasks have finished. Exiting.")
    # Workaround:
    # Fargate will exit and final batch of CloudWatch logs will be lost
    sleep(10)

答案 3 :(得分:0)

我在将日志刷新到CloudWatch时遇到了同样的问题。

在回答asavoy's之后,我从exec形式切换为ENTRYPOINT的shell形式,并在最后添加了10秒的睡眠时间。

之前:

ENTRYPOINT ["java","-jar","/app.jar"]

之后:

ENTRYPOINT java -jar /app.jar; sleep 10