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
相关产品推荐
相关产品推荐

