Cloud Run Flask API 容器运行 shutit 进入休眠循环

Posted

技术标签:

【中文标题】Cloud Run Flask API 容器运行 shutit 进入休眠循环【英文标题】:Cloud Run Flask API container running shutit enters a sleep loop 【发布时间】:2021-11-14 20:10:20 【问题描述】:

这个问题最近出现了,以前健康的容器现在在创建shutit会话时进入睡眠循环。该问题仅在 Cloud Run 上出现,而不是在本地出现。

最小可重现代码:

requirements.txt

Flask==2.0.1
gunicorn==20.1.0
shutit

Dockerfile

FROM python:3.9

# Allow statements and log messages to immediately appear in the Cloud Run logs
ENV PYTHONUNBUFFERED True

COPY requirements.txt ./
RUN pip install -r requirements.txt

# Copy local code to the container image.
ENV APP_HOME /myapp
WORKDIR $APP_HOME
COPY . ./

CMD exec gunicorn \
 --bind :$PORT \
 --worker-class "sync" \
 --workers 1 \
 --threads 1 \
 --timeout 0 \
 main:app

main.py

import os
import shutit
from flask import Flask, request

app = Flask(__name__)

# just to prove api works
@app.route('/ping', methods=['GET'])
def ping():
    os.system('echo pong')
    return 'OK'

# issue replication
@app.route('/healthcheck', methods=['GET'])
def healthcheck():
    os.system("echo 'healthcheck'")
    # hangs inside create_session
    shell = shutit.create_session(echo=True, loglevel='debug')
    # never shell.send reached 
    shell.send('echo Hello World', echo=True)
    # never returned
    return 'OK'

if __name__ == '__main__':
    app.run(host='127.0.0.1', port=8080, debug=True)

cloudbuild.yaml

steps:
  - id: "build_container"
    name: "gcr.io/kaniko-project/executor:latest"
    args:
      - --destination=gcr.io/$PROJECT_ID/borked-service-debug:latest
      - --cache=true
      - --cache-ttl=99h
  - id: "configure infrastructure"
    name: "gcr.io/cloud-builders/gcloud"
    entrypoint: "bash"
    args:
      - "-c"
      - |
        set -euxo pipefail

        REGION="europe-west1"
        CLOUD_RUN_SERVICE="borked-service-debug"

        SA_NAME="$$CLOUD_RUN_SERVICE@$PROJECT_ID.iam.gserviceaccount.com"

        gcloud beta run deploy $$CLOUD_RUN_SERVICE \
          --service-account "$$SA_NAME" \
          --image gcr.io/$PROJECT_ID/$$CLOUD_RUN_SERVICE:latest \
          --allow-unauthenticated \
          --platform managed \
          --concurrency 1 \
          --max-instances 10 \
          --timeout 1000s \
          --cpu 1 \
          --memory=1Gi \
          --region "$$REGION"

被循环的云运行日志:

Setting up prompt
In session: host_child, trying to send: export PS1_ORIGIN_ENV=$PS1 && PS1='OR''IGIN_ENV:rkkfQQ2y# ' && PROMPT_COMMAND='sleep .05||sleep 1'
================================================================================
Sending>>> export PS1_ORIGIN_ENV=$PS1 && PS1='OR''IGIN_ENV:rkkfQQ2y# ' && PROMPT_COMMAND='sleep .05||sleep 1'<<<, expecting>>>['\r\nORIGIN_ENV:rkkfQQ2y# ']<<<
Sending in pexpect session (68242035994000): export PS1_ORIGIN_ENV=$PS1 && PS1='OR''IGIN_ENV:rkkfQQ2y# ' && PROMPT_COMMAND='sleep .05||sleep 1'
Expecting: ['\r\nORIGIN_ENV:rkkfQQ2y# ']
export PS1_ORIGIN_ENV=$PS1 && PS1='OR''IGIN_ENV:rkkfQQ2y# ' && PROMPT_COMMAND='sleep .05||sleep 1'
root@localhost:/myapp# export PS1_ORIGIN_ENV=$PS1 && PS1='OR''IGIN_ENV:rkkfQQ2y# ' && PROMPT_COMMAND='sleep .05||sleep 1'
Stopped sleep .05
Stopped sleep 1
pexpect: buffer: b'' before: b'cm9vdEBsb2NhbGhvc3Q6L3B1YnN1YiMgIGV4cx' after: b'DQpPUklHSU5fRU5WOnJra2ZRUTJ5IyA='
Resetting default expect to: ORIGIN_ENV:rkkfQQ2y# 
In session: host_child, trying to send: stty cols 65535
================================================================================
Sending>>> stty cols 65535<<<, expecting>>>ORIGIN_ENV:rkkfQQ2y# <<<
Sending in pexpect session (68242035994000): stty cols 65535
Expecting: ORIGIN_ENV:rkkfQQ2y# 
ORIGIN_ENV:rkkfQQ2y# stty cols 65535
stty cols 65535
Stopped stty cols 65535
Stopped sleep .05
Stopped sleep 1

尝试过的解决方法:

不同的地区:一些欧洲(1 级和 2 级)、亚洲、美国。 使用 docker 而不是 kaniko 构建 分配给容器的不同 CPU 和内存 Minimum number of containers 1-5 (to ensure CPU is always allocated to the container) --no-cpu-throttling 也没有区别 最大容器数1-30 不同的 GCP 项目 不同的 Docker 基础镜像(3.5-3.9 + 从一年前到最近的各种 shas)

【问题讨论】:

Cloud Run 不支持后台任务。当您的 Flask 应用返回 HTTP 响应时,Cloud Run 将空闲 CPU。然后,您的后台任务将没有 CPU 时间。 这是新的限制吗?因为直到上周四为止,它一直运行良好。 不,这不是新的限制,自第一个版本以来就已记录在案。你只是很幸运。 cloud.google.com/run/docs/tips/general 不确定我是否关注。它并没有真正用作后台任务,因为在shutit工作完成之前不会返回http响应。因此仍应分配 CPU。我可以在 Cloud Run 仪表板中看到 CPU 已分配给容器。这挂起shell = shutit.create_session(echo=True, loglevel='debug') 这永远不会执行shell.send('echo Hello World', echo=True)。永不返回return 'OK'``` 您阅读我发送的文档链接了吗?您的应用程序被打包在一个容器中。当您收到 HTTP 请求时,CPU 将分配给正在运行的线程。执行模型是 HTTP 请求/响应。 ShutitPexpect 的包装器,Pexpect 是用于生成子应用程序的 Python 模块。子应用与 Cloud Run 线程异步运行。 【参考方案1】:

我已经重现了您的问题,并且我们讨论了几种可能性,我认为问题是您的 Cloud Run 无法处理请求并因此准备关闭(sigterm)。 我列出了一些可能性供您查看和分析。

您的 Cloud Run 服务无法启动的一个很好的原因是 容器内的服务器进程被配置为监听 本地主机(127.0.0.1)地址。这是指环回网络 接口,不能从容器外部访问,并且 因此无法执行 Cloud Run 运行状况检查,导致 服务部署失败。要解决此问题,请配置您的应用程序 启动 HTTP 服务器以侦听所有网络接口, 一般记为0.0.0.0。

在搜索您遇到的云日志错误时,我来了 穿过来自shutit库的answer和GitHub link 开发人员指出了一种跟踪输入和输出的技术 在复杂的容器中构建 shutit 会话。一个很好的发现 从 GitHub 链接,我想你必须通过 session_type 在shutit.create_session(‘bash’)shutit.create_session(‘docker’) 您没有在 main.py 文件中指定。那可以是 关闭会话失败的原因。

这个问题也可能是由于使用了一些 Linux 内核特性 这个shutit库目前没有被正确支持 gVisor 。我不确定它是如何为你执行的 时间。大多数应用程序都可以正常运行,或者至少与常规应用程序一样好 Docker,但可能无法提供 100% 的兼容性。

Cloud Run 应用程序在 gVisor 容器沙箱上运行(支持 目前仅适用于 Linux),它执行 Linux 内核系统调用 由您在用户空间中的应用程序。 gVisor 没有实现所有 系统调用(参见here)。从这个Github link,“如果你 应用程序有这样的系统调用(很少见),它不会在云上运行 跑步。这样的事件是logged,您可以使用strace 确定何时在您的应用中进行系统调用”

如果您在 Linux 上运行代码,请安装并启用 strace: sudo apt-get install strace 使用 strace 运行您的应用程序 用 strace -f 在你通常的调用前加上 -f 的意思 跟踪所有子线程。例如,如果您通常调用您的 使用./main 的应用程序,您可以通过调用/usr/bin/strace -f ./main 使用strace 运行它

来自documentation,“如果您认为您的问题是由 容器沙箱中的一个限制。在 Cloud Logging 部分 GCP 控制台(不在 Cloud Run 部分的“日志”标签中), 您可以在DEBUG 严重性中查找Container Sandbox varlog/system日志或使用日志查询:

resource.type="cloud_run_revision"
logName="projects/PROJECT_ID/logs/run.googleapis.com%2Fvarlog%2Fsystem"

例如:容器沙箱:不支持的系统调用 setsockopt(0x3,0x1,0x6,0xc0000753d0,0x4,0x0)”

默认情况下,容器实例关闭最小实例,设置为 0。我们可以使用 Cloud Console、gcloud 命令行或 YAML 文件更改此默认设置,方法是指定容器实例的最小数量保持温暖并准备好响应请求。

您还可以查看此documentation 和GitHub Link,其中讨论了 Cloud Run 容器运行时行为和故障排除以供参考。

【讨论】:

嗨 Priyashree,非常感谢您提供非常详细的回复!我已经一一浏览了您记录的提示:- 0.0.0.0 端口。很棒的建议,希望我之前尝试过,但不幸的是没有骰子。还是卡住了。 - create_sessionbash 命名的参数很遗憾也没有带来任何结果。 - strace 是史诗般的!我在启动时收到了Unsupported syscall process_vm_readv。不幸的是,我很难说这是正常的还是正常的。你怎么看?在最初的故障排除过程中,我确实尝试了--min-instances,但这并没有影响结果 好的,现在我希望您尝试使用这些instructions 在 Docker 上本地运行您的应用程序,并验证您的应用程序在本地是否可以正常启动? 容器还必须在请求发送到的端口上侦听 0.0.0.0 上的请求。默认情况下,请求发送到 8080。在 cloudbuild.yaml 文件中添加 --min-instances 并暂时给它一些值,然后尝试。 @alarmynah 有什么更新吗? 当然,非常感谢!【参考方案2】:

这不是一个完美的替代品,但您可以使用以下之一:

我不确定大局是什么,所以我会添加各种选项

对于来自烧瓶网络服务器的远程自动化任务,我们使用paramiko 是因为它的简单性和快速设置,但您可能更喜欢pyinfra 用于大型项目或subprocess 用于小型本地 任务。

    Paramiko - 比 shutit 更需要动手\手动操作,通过 ssh 协议运行命令。

示例:

import paramiko

ip='server ip'
port=22
# you can also use ssh keys
username='username'
password='password'

cmd='some useful command' 

ssh=paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(ip,port,username,password)

stdin,stdout,stderr=ssh.exec_command(cmd)
outlines=stdout.readlines()
resp=''.join(outlines)
print(resp)

more examples

    pyinfra - ansible like library 以 ad-hoc 风格自动执行任务

使用 apt 安装包的示例:

from pyinfra.operations import apt

apt.packages(
    name='Ensure iftop is installed',
    packages=['iftop'],
    sudo=True,
    update=True,
)
    subprocess - 就像 Paramiko 一样,不像 shutit 那样广泛,但像魅力一样工作

【讨论】:

感谢您的回复!这是一个简单的 shutit 示例,因为该应用程序使用它的范围更广,我只是想缩小到最小的可能重现示例。但可能会试一试,看看使用您提供的建议重写是否会很快。非常感谢 @alanmynah 很高兴它帮助了你,它解决了你的问题吗? 害怕,不是根本问题,但也非常感谢解决方法! 如果您找不到解决问题的答案,我将不胜感激接受答案

以上是关于Cloud Run Flask API 容器运行 shutit 进入休眠循环的主要内容,如果未能解决你的问题,请参考以下文章