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 请求/响应。 Shutit 是 Pexpect 的包装器,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_session
和 bash
命名的参数很遗憾也没有带来任何结果。 - 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 容器
如何检查正在运行的 Google Cloud Run 容器的实例数?
Cloud Run 中的 API 和 VM 中的 Nginx 反向代理