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

尝试过的解决方法:

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

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

对于来自 Flask Web 服务器的远程自动化任务,我们使用 paramiko 是因为它的简单性和快速设置,尽管您可能更喜欢 pyinfra 用于大型项目或 subprocess对于小型 local 任务。

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

more examples

  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 广泛,但很有魅力

我已经重现了您的问题,我们已经讨论了几种可能性,我认为问题是您的云 运行 无法处理请求,因此准备关闭 (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 运行 容器 运行时间行为和故障排除以供参考。