如何在Docker容器启动所有服务后自动执行指定Bash脚本?
我在Docker容器中部署了一个Flask API,项目用来爬取学校系统的全部课程表,为学生生成所有可行的课程表组合,目前运行正常。但有一个需要手动调用的爬取学校网页的接口/FetchGroupDataUPSite,这是后续功能运行的核心前提。我写了一个Bash脚本通过GET请求调用该接口,现在想实现每次构建Docker容器并启动所有服务后自动运行这个脚本,同时希望得到其他更优的解决思路。
以下是现有脚本、docker-compose.yml和Dockerfile:
调用接口的Bash脚本
#!/bin/bash URL="http://localhost:5555/FetchGroupDataUPSite" max_retries=3 retries=0 delay=5 echo "Sending GET request to scrapper endpoint..." while [ $retries -lt $max_retries ] do response=$(curl -s -o /dev/null -w "%{http_code}" -X GET $URL) if [ $response -eq 200 ] then echo "Scrapper script completed." break else echo "Request failed with status code $response. Retrying in $delay seconds..." retries=$((retries+1)) sleep $delay fi done if [ $retries -eq $max_retries ] then echo "Maximum number of retries reached. Scrapper script failed." fi
docker-compose.yml
version: '3.8' networks: app-tier: driver: bridge services: db: image: mysql:5.7 container_name: UP_DB restart: always environment: MYSQL_ROOT_PASSWORD: *** MYSQL_PASSWORD: *** MYSQL_DATABASE: *** MYSQL_USER: *** command: --default-authentication-plugin=mysql_native_password --bind-address=0.0.0.0 --explicit_defaults_for_timestamp volumes: - ./sql:/docker-entrypoint-initdb.d ports: - 3306:3306 networks: - app-tier adminer: image: adminer container_name: UP_Adminer restart: always ports: - 8080:8080 networks: - app-tier api: container_name: UP_API stdin_open: true tty: true restart: always build: . ports: - 5555:5555 volumes: - ./App:/SmartUP/App networks: - app-tier
Dockerfile
FROM python:3.10.7-buster WORKDIR /SmartUP COPY . . ENV FLASK_ENV development ENV DEBUG true RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone RUN apt-get install -y wget RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - RUN sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list' RUN apt-get -y update RUN apt-get install -y google-chrome-stable RUN apt-get install -yqq unzip RUN wget -O /tmp/chromedriver.zip http://chromedriver.storage.googleapis.com/`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE`/chromedriver_linux64.zip # Unzip the Chrome Driver into /usr/local/bin directory RUN unzip /tmp/chromedriver.zip chromedriver -d /usr/local/bin/ # Set display port as an environment variable ENV DISPLAY=:99 RUN pip install --upgrade pip && pip3 install -r requirements.txt CMD ["python", "App/run.py"] # Makes the file executable EXPOSE 5555
方法一:在API容器启动后自动执行脚本
可以通过修改Dockerfile或docker-compose配置,让Flask服务启动后自动触发爬取脚本。
方式1:用ENTRYPOINT+启动脚本组合
创建一个start.sh启动脚本,后台启动Flask服务后,执行带重试的爬取调用:
#!/bin/bash # 后台启动Flask服务 python App/run.py & FLASK_PID=$! # 调用爬取接口(改用容器网络内的服务地址,更可靠) URL="http://localhost:5555/FetchGroupDataUPSite" max_retries=3 retries=0 delay=5 echo "Waiting for Flask API to be ready..." while [ $retries -lt $max_retries ] do response=$(curl -s -o /dev/null -w "%{http_code}" -X GET $URL) if [ $response -eq 200 ] then echo "Scrapper script completed." break else echo "Request failed with status code $response. Retrying in $delay seconds..." retries=$((retries+1)) sleep $delay fi done if [ $retries -eq $max_retries ] then echo "Maximum number of retries reached. Scrapper script failed." fi # 等待Flask进程,避免容器提前退出 wait $FLASK_PID
修改Dockerfile,将启动脚本复制到容器并设置为入口:
# ... 保留原有所有配置 ... # 复制启动脚本并赋予执行权限 COPY start.sh /SmartUP/start.sh RUN chmod +x /SmartUP/start.sh # 替换原有CMD为ENTRYPOINT ENTRYPOINT ["/SmartUP/start.sh"]
方式2:直接在docker-compose中指定命令
如果不想修改Dockerfile,可以在api服务的command字段中串联启动命令:
api: # ... 保留原有配置 ... command: bash -c "python App/run.py & sleep 8 && /SmartUP/your-script.sh && wait"
注意:需要将本地脚本挂载到容器内(比如通过volume),或者在Dockerfile中复制进去;sleep 8是简单等待,不如自带重试的脚本可靠,优先推荐方式1。
方法二:修改Flask应用,启动时自动执行爬取逻辑
这是最直接的优化方案——跳过HTTP请求,直接在Flask启动时调用爬取函数:
from flask import Flask # 导入你的爬取核心函数,比如fetch_group_data_up_site app = Flask(__name__) # ... 其他路由、配置代码 ... if __name__ == '__main__': # 先执行爬取逻辑 print("Starting course data scrapping...") try: fetch_group_data_up_site() print("Scrapping completed successfully.") except Exception as e: print(f"Scrapping failed: {str(e)}") # 启动Flask服务 app.run(host='0.0.0.0', port=5555)
这种方式省去了额外的HTTP请求,减少了依赖点,效率和可靠性都更高。
方法三:新增独立的初始化服务到docker-compose
在docker-compose中添加一个专门执行爬取调用的服务,依赖API服务就绪后再运行:
- 给api服务添加健康检查,确保服务真正可用:
api: # ... 保留原有配置 ... healthcheck: test: ["CMD", "curl", "-f", "http://localhost:5555/FetchGroupDataUPSite"] interval: 5s timeout: 5s retries: 3
- 添加初始化服务:
version: '3.8' networks: app-tier: driver: bridge services: # ... db、adminer、api服务保留原有配置 ... init-scrapper: image: curlimages/curl:latest volumes: - ./your-script.sh:/script.sh command: bash /script.sh depends_on: api: condition: service_healthy networks: - app-tier
注意:需要把脚本中的URL改成http://api:5555/FetchGroupDataUPSite,因为在docker-compose网络中,服务名称可直接作为域名使用。
内容的提问来源于stack exchange,提问作者Luis Cedillo Maldonado

