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 进入休眠循环的主要内容,如果未能解决你的问题,请参考以下文章

输入使用 Google Cloud Run 运行的 docker 容器

您可以在 Cloud Run 容器中运行沙盒容器吗?

如何检查正在运行的 Google Cloud Run 容器的实例数?

Cloud Run 中的 API 和 VM 中的 Nginx 反向代理

Python 线程在 Docker 容器中并行运行,但在容器在 Google Cloud Run 上运行时按顺序运行

Cloud Run 上的容器内存管理和 OOM