Cloud Run Flask API容器运行后进入了睡眠循环。

6
该问题最近出现,之前健康的容器在创建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}"

循环访问Cloud Run日志:

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 和内存大小。
  • 最小容器数为 1-5(以确保 CPU 始终分配给容器)
  • --no-cpu-throttling 也没有改变情况。
  • 最大容器数为 1-30。
  • 使用不同的 GCP 项目。
  • 使用不同的 Docker 基础映像(3.5-3.9 + 不同的 shas,从一年前到最近的版本)。

这是一个新的限制吗?因为直到上周四为止,一切都运行得非常完美。 - alanmynah
1
不,这不是新的限制,自第一次发布以来就已经有记录了。你只是幸运而已。https://cloud.google.com/run/docs/tips/general - John Hanley
前往日志。如果 shell.send() 从未返回,则您的 Cloud Run 线程应该挂起。Cloud Run 将终止容器,您将看到错误日志条目。与其就此问题进行辩论,不如收集提供有关应用程序实际发生情况的详细数据。 - John Hanley
你已经回答了自己的问题。如果你的应用程序调用sleep太多次,Cloud Run会杀死容器。不要调用sleep。Cloud Run不是异步运行时系统。 - John Hanley
没错。然后突然就停止工作了。甚至在注释中都无法链接到任何特定版本。 - alanmynah
显示剩余14条评论
2个回答

2
我已经复现了您的问题,并讨论了几种可能性,我认为问题在于您的Cloud Run无法处理请求,因此准备关闭(sigterm)。 我列出了一些可能性供您查看和分析。
您的 Cloud Run 服务启动失败的一个很好的原因是容器内部的服务器进程配置为侦听本地主机(127.0.0.1)地址。这指的是回环网络接口,它无法从容器外部访问,因此无法执行 Cloud Run 健康检查,导致服务部署失败。要解决此问题,请将应用程序配置为启动 HTTP 服务器以侦听所有网络接口,通常表示为 0.0.0.0。
在搜索您遇到的云日志错误时,我看到了来自 shutit 库开发人员的 答案GitHub 链接,其中指出了一种在 shutit 会话中跟踪输入和输出的技术。从 GitHub 链接中得到的一个好的发现是,我认为您需要在 shutit.create_session('bash')shutit.create_session('docker') 中传递 session_type,在 main.py 文件中没有指定。这可能是您的 shutit 会话失败的原因。
此外,此问题可能是由于 shutit 库使用的某些 Linux 内核功能目前在 gVisor 中未得到正确支持。我不确定第一次如何执行它。大多数应用程序都能正常工作,或者至少与常规 Docker 一样好,但可能无法提供 100% 的兼容性。
Cloud Run 应用程序在 gVisor 容器沙箱中运行(目前仅支持 Linux),该沙箱在用户空间执行应用程序发出的 Linux 内核系统调用。gVisor 不实现所有系统调用(请参见 此处)。从这个 Github 链接 中,“如果您的应用程序具有这样的系统调用(相当罕见),它将无法在 Cloud Run 上工作。这种情况被记录下来,您可以使用 strace 来确定系统调用何时在您的应用程序中进行了调用。”
如果您在 Linux 上运行代码,请安装并启用 strace:sudo apt-get install strace 通过在通常的调用前加上 strace -f 来运行带有 strace 的应用程序,其中 -f 表示跟踪所有子线程。例如,如果您通常使用 ./main 调用应用程序,则可以通过调用 /usr/bin/strace -f ./main 来运行它。
从这个 文档 中,“如果您认为问题是由容器沙箱的限制引起的。在 GCP 控制台的 Cloud Logging 部分(而不是 Cloud Run 部分的“日志”选项卡)中,您可以查找 varlog/system 日志中带有 DEBUG 严重性的 Container Sandbox,或使用 Log Query:”。
resource.type="cloud_run_revision"
logName="projects/PROJECT_ID/logs/run.googleapis.com%2Fvarlog%2Fsystem"
例如:容器沙箱:不支持的系统调用
setsockopt(0x3,0x1,0x6,0xc0000753d0,0x4,0x0)”
默认情况下,容器实例的min-instances被关闭,设置为0。我们可以使用Cloud Console、gcloud命令行或YAML文件来更改此默认值,通过指定要保持温暖并准备好提供请求的最小容器实例数。
您还可以查看这个文档GitHub链接,了解有关Cloud Run容器运行时行为和故障排除的参考信息。

好的,现在我想让你尝试使用这些说明在Docker上本地运行你的应用程序,并验证应用程序是否可以在本地正常启动? - Priyashree Bhadra
@alanmynah 有更新吗? - Priyashree Bhadra
嗨Priyashree,真的很抱歉让你等了这么久。我们创建了一个GKE集群来解决问题,现在我回来了。我已经按照本地VS Code设置的说明进行了操作。那太棒了,我不知道这个,https://cloud.google.com/code/docs/vscode/developing-a-cloud-run-service但不幸的是它没有起作用。或者幸运的是。不确定你的想法是什么?容器在8080上运行,我可以curl到两个端点,并且我可以在Cloud Run: Run/Debug VSCode输出选项卡中看到shutit输出。 - alanmynah
是的,根据我们在调试过程中遵循的步骤,我相信这是一个Cloud Run沙盒问题。您也可以继续使用GKE,但是请注意,Cloud Run for Anthos只是Cloud Run完全托管的适当替代品,特别适用于这些情况。如果您认为我的答案对您有帮助,请考虑接受它。谢谢,祝您拥有愉快的一天! - Priyashree Bhadra
1
当然,非常感谢! - alanmynah
显示剩余5条评论

0

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

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

对于从flask web服务器进行远程自动化任务,我们使用paramiko,因为它简单易用且快速设置,尽管您可能更喜欢像pyinfra这样的工具来处理大型项目或subprocess来处理小型本地任务。

  1. 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)

更多示例

  1. pyinfra - 类似ansible的库,以即席方式自动化任务

通过apt安装软件包的示例:

from pyinfra.operations import apt

apt.packages(
    name='Ensure iftop is installed',
    packages=['iftop'],
    sudo=True,
    update=True,
)
  1. subprocess - 就像 Paramiko 一样,不像 shutit 那么复杂,但运行起来非常顺畅。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接