Cloud 运行 Flask API container 运行 shutit 进入休眠循环
Cloud Run Flask API container running shutit enters a sleep loop
最近出现了这个问题,之前健康的容器现在在创建 shutit 会话时进入睡眠循环。该问题仅发生在云端 运行 而不是本地。
最小可重现代码:
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)
它不是完美的替代品,但您可以使用以下其中一种替代品:
我不确定大局是什么,所以我会添加各种选项
对于来自 Flask Web 服务器的远程自动化任务,我们使用 paramiko
是因为它的简单性和快速设置,尽管您可能更喜欢 pyinfra
用于大型项目或 subprocess
对于小型 local 任务。
- Paramiko - 比
shutit
、运行 通过 ssh 协议的命令多 hands-on\manual。
示例:
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)
- pyinfra - 类似 ansible 的库以临时方式自动执行任务
使用 apt 安装包的示例:
from pyinfra.operations import apt
apt.packages(
name='Ensure iftop is installed',
packages=['iftop'],
sudo=True,
update=True,
)
- subprocess - 像 Paramiko 不像
shutit
广泛,但很有魅力
我已经重现了您的问题,我们已经讨论了几种可能性,我认为问题是您的云 运行 无法处理请求,因此准备关闭 (sigterm)。
我列出了一些可能性供您查看和分析。
云 运行 服务无法启动的一个很好的原因是
容器内的服务器进程被配置为监听
本地主机 (127.0.0.1) 地址。这是指环回网络
接口,无法从容器外部访问,并且
因此 Cloud 运行 无法执行健康检查,导致
服务部署失败。要解决此问题,请配置您的应用程序
启动 HTTP 服务器以侦听所有网络接口,
通常表示为 0.0.0.0.
在搜索你遇到的云日志错误时,我来了
来自 shutit 图书馆的 answer and GitHub link
开发人员指出跟踪输入和输出的技术
在复杂的容器中构建在 shutit 会话中。一个很好的发现
从 GitHub link,我想你必须通过 session_type
在 shutit.create_session(‘bash’)
或 shutit.create_session(‘docker’)
您没有在 main.py 文件中指定。那可以是
您的 shutit 会话失败的原因。
这个问题也可能是由于某些 Linux 内核特性引起的
这个 shutit 库目前在
gVisor。我不确定它是如何首先为你执行的
时间。大多数应用程序都可以正常运行,或者至少与常规应用程序一样好
Docker,但可能无法提供 100% 的兼容性。
gVisor 容器沙箱上的云 运行 应用程序 运行(支持
Linux 仅当前),执行 Linux 内核系统调用
由您在用户空间中的应用程序。 gVisor 没有实现所有
系统调用(参见 here). From this Github link,“如果你的
应用程序有这样的系统调用(很少见),它不会在云端运行
运行。这样的事件是 logged and you can use strace
确定在您的应用中进行系统调用的时间”
如果您 运行 在 Linux 上安装代码,请安装并启用 strace:
sudo apt-get install strace
运行 你的 strace 应用
使用 strace -f 作为您通常调用的开头,其中 -f 表示
跟踪所有子线程。例如,如果您通常调用您的
使用 ./main
的应用程序,您可以 运行 通过调用 /usr/bin/strace -f ./main
使用 strace
由此 documentation,“如果您觉得您的问题是由
容器沙箱中的限制。在云日志部分
GCP 控制台的(不在 Cloud 运行 部分的“日志”选项卡中),
您可以在 Container Sandbox
中查找 DEBUG
严重性
varlog/system
记录或使用日志查询:
resource.type="cloud_run_revision"
logName="projects/PROJECT_ID/logs/run.googleapis.com%2Fvarlog%2Fsystem"
For example: Container Sandbox: Unsupported syscall
setsockopt(0x3,0x1,0x6,0xc0000753d0,0x4,0x0)”
默认情况下,容器实例已关闭最小实例,设置为 0。我们可以使用 Cloud Console、gcloud 命令行或 YAML 文件更改此默认值,方法是指定容器的最小数量实例保持温暖并准备好服务请求。
您还可以查看此 documentation and GitHub Link,其中讨论了 Cloud 运行 容器 运行时间行为和故障排除以供参考。
最近出现了这个问题,之前健康的容器现在在创建 shutit 会话时进入睡眠循环。该问题仅发生在云端 运行 而不是本地。
最小可重现代码:
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)
它不是完美的替代品,但您可以使用以下其中一种替代品:
我不确定大局是什么,所以我会添加各种选项
对于来自 Flask Web 服务器的远程自动化任务,我们使用 paramiko
是因为它的简单性和快速设置,尽管您可能更喜欢 pyinfra
用于大型项目或 subprocess
对于小型 local 任务。
- Paramiko - 比
shutit
、运行 通过 ssh 协议的命令多 hands-on\manual。
示例:
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)
- pyinfra - 类似 ansible 的库以临时方式自动执行任务
使用 apt 安装包的示例:
from pyinfra.operations import apt
apt.packages(
name='Ensure iftop is installed',
packages=['iftop'],
sudo=True,
update=True,
)
- subprocess - 像 Paramiko 不像
shutit
广泛,但很有魅力
我已经重现了您的问题,我们已经讨论了几种可能性,我认为问题是您的云 运行 无法处理请求,因此准备关闭 (sigterm)。 我列出了一些可能性供您查看和分析。
云 运行 服务无法启动的一个很好的原因是 容器内的服务器进程被配置为监听 本地主机 (127.0.0.1) 地址。这是指环回网络 接口,无法从容器外部访问,并且 因此 Cloud 运行 无法执行健康检查,导致 服务部署失败。要解决此问题,请配置您的应用程序 启动 HTTP 服务器以侦听所有网络接口, 通常表示为 0.0.0.0.
在搜索你遇到的云日志错误时,我来了 来自 shutit 图书馆的 answer and GitHub link 开发人员指出跟踪输入和输出的技术 在复杂的容器中构建在 shutit 会话中。一个很好的发现 从 GitHub link,我想你必须通过 session_type 在
shutit.create_session(‘bash’)
或shutit.create_session(‘docker’)
您没有在 main.py 文件中指定。那可以是 您的 shutit 会话失败的原因。这个问题也可能是由于某些 Linux 内核特性引起的 这个 shutit 库目前在 gVisor。我不确定它是如何首先为你执行的 时间。大多数应用程序都可以正常运行,或者至少与常规应用程序一样好 Docker,但可能无法提供 100% 的兼容性。
gVisor 容器沙箱上的云 运行 应用程序 运行(支持 Linux 仅当前),执行 Linux 内核系统调用 由您在用户空间中的应用程序。 gVisor 没有实现所有 系统调用(参见 here). From this Github link,“如果你的 应用程序有这样的系统调用(很少见),它不会在云端运行 运行。这样的事件是 logged and you can use strace 确定在您的应用中进行系统调用的时间”
如果您 运行 在 Linux 上安装代码,请安装并启用 strace:
使用 stracesudo apt-get install strace
运行 你的 strace 应用 使用 strace -f 作为您通常调用的开头,其中 -f 表示 跟踪所有子线程。例如,如果您通常调用您的 使用./main
的应用程序,您可以 运行 通过调用/usr/bin/strace -f ./main
由此 documentation,“如果您觉得您的问题是由 容器沙箱中的限制。在云日志部分 GCP 控制台的(不在 Cloud 运行 部分的“日志”选项卡中), 您可以在
Container Sandbox
中查找DEBUG
严重性varlog/system
记录或使用日志查询:
resource.type="cloud_run_revision" logName="projects/PROJECT_ID/logs/run.googleapis.com%2Fvarlog%2Fsystem"
For example: Container Sandbox: Unsupported syscall
setsockopt(0x3,0x1,0x6,0xc0000753d0,0x4,0x0)”
默认情况下,容器实例已关闭最小实例,设置为 0。我们可以使用 Cloud Console、gcloud 命令行或 YAML 文件更改此默认值,方法是指定容器的最小数量实例保持温暖并准备好服务请求。
您还可以查看此 documentation and GitHub Link,其中讨论了 Cloud 运行 容器 运行时间行为和故障排除以供参考。