You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Cloud Run环境下含shutit的Flask API容器创建会话时进入睡眠循环问题

问题概述

该问题近期出现,原本运行正常的容器在创建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
已尝试的无效解决方案
  • 切换多个区域:多个欧洲区域(一级、二级)、亚洲、美国区域
  • 使用docker替代kaniko构建镜像
  • 调整容器分配的CPU和内存规格
  • 设置最小容器实例数为1-5(确保容器始终分配CPU资源)
  • 配置--no-cpu-throttling参数无改善
  • 调整最大容器实例数为1-30
  • 切换不同GCP项目
  • 使用不同Docker基础镜像(3.5-3.9版本,以及过往一年至今的多个镜像sha版本)
根因分析

核心原因是Cloud Run 全托管环境不支持分配交互式伪终端(PTY),shutit底层基于pexpect实现,默认创建会话时会自动修改shell提示符、设置带sleep循环的PROMPT_COMMAND用于匹配提示符,在无PTY的环境下,提示符输出的换行、格式不符合shutit的默认匹配规则,导致pexpect永远无法匹配到预期的提示符,进入死循环。该问题近期触发大概率是Cloud Run容器运行时的PTY模拟逻辑更新,或是shutit版本迭代后默认匹配规则更严格导致。

可行解决方案

方案1:调整shutit会话参数(推荐)

修改main.py中创建shutit会话的代码,禁用自动提示符配置、手动指定匹配规则,避免触发sleep循环:

shell = shutit.create_session(
    echo=True,
    loglevel='debug',
    noprofile=True,
    norc=True,
    disable_progress=True,
    prompt=r'root@.*[:/].*# '
)

该方案无需修改基础设施,代码改动最小即可适配Cloud Run环境。

方案2:替换为非交互式命令执行

如果不需要shutit的交互式shell控制能力,直接用Python标准库subprocess.run执行命令,完全规避PTY依赖:

# 替换shutit相关代码
import subprocess
result = subprocess.run(['echo', 'Hello World'], capture_output=True, text=True)
print(result.stdout)

方案3:切换到支持PTY的Cloud Run部署模式

如果必须使用shutit的全量交互式功能,将服务切换到Cloud Run on GKE(Anthos)部署,该环境支持配置容器分配TTY/PTY,兼容shutit的默认运行逻辑。

内容的提问来源于stack exchange,提问作者alanmynah

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 09:57:03