Skip to content

将 Yii 应用部署到 Docker Swarm 和 Traefik

本指南将引导你从空白服务器开始,使用 Traefik 作为反向代理,从容器注册表(ForgejoGitea)部署 Yii 应用程序到 Docker Swarm

mermaid
graph LR
    A[Internet] --> B[Reverse Proxy: Traefik]
    B --> C[app1.example.com]
    B --> D[app2.example.com]
    
    subgraph Docker Swarm Cluster
        B
        C
        D
    end

前提条件

  • 一台全新安装 Linux 发行版的服务器(推荐 Ubuntu 22.04 LTS 或更高版本)
  • 指向服务器 IP 地址的域名
  • 对服务器的 SSH 访问权限
  • Docker 和命令行工具的基础知识

服务器准备

安装 Docker

有关安装说明,请参阅官方 Docker 文档

初始化 Docker Swarm

将服务器初始化为 Docker Swarm 管理器:

bash
docker swarm init --advertise-addr <YOUR_SERVER_IP>

<YOUR_SERVER_IP> 替换为服务器的公共 IP 地址。

设置反向代理网络

创建一个专用的覆盖网络,供反向代理与服务通信:

bash
docker network create --driver=overlay reverse_proxy_public

设置 Traefik 作为反向代理

要将 Traefik 部署为反向代理,请创建文件 traefik-stack.yml

yaml
services:
  traefik:
    image: traefik:v2.10
    command:
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.swarmMode=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
      - "--certificatesresolvers.letsencrypt.acme.email=admin@example.com"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "traefik_certificates:/letsencrypt"
    networks:
      - reverse_proxy_public
    deploy:
      placement:
        constraints:
          - node.role == manager

volumes:
  traefik_certificates:

networks:
  reverse_proxy_public:
    external: true

部署 Traefik:

bash
docker stack deploy -c traefik-stack.yml traefik

设置容器注册表

你需要一个容器注册表来存储 Docker 镜像。选择以下选项之一。

选项 1:使用 Forgejo

要部署 Forgejo,请创建文件 forgejo-stack.yml

yaml
services:
  forgejo:
    image: codeberg.org/forgejo/forgejo:1.21
    ports:
      - "3000:3000"
    volumes:
      - forgejo_data:/data
    networks:
      - reverse_proxy_public
    deploy:
      labels:
        - "traefik.enable=true"
        - "traefik.http.routers.app.rule=Host(`git.example.com`)"
        - "traefik.http.routers.app.entrypoints=websecure"
        - "traefik.http.routers.app.tls.certresolver=letsencrypt"
        - "traefik.http.services.app.loadbalancer.server.port=3000"        

volumes:
  forgejo_data:

networks:
  reverse_proxy_public:
    external: true

部署 Forgejo:

bash
docker stack deploy -c forgejo-stack.yml forgejo

git.example.com 替换为所需的子域名。

部署后,访问 https://git.example.com 上的 Forgejo 并完成初始设置。确保在设置中启用容器注册表。

选项 2:使用 Gitea

要部署 Gitea,请创建文件 gitea-stack.yml

yaml
services:
  gitea:
    image: gitea/gitea:latest
    ports:
      - "3000:3000"
    volumes:
      - gitea_data:/data
    networks:
      - reverse_proxy_public
    deploy:
      labels:
        - "traefik.enable=true"
        - "traefik.http.routers.app.rule=Host(`git.example.com`)"
        - "traefik.http.routers.app.entrypoints=websecure"
        - "traefik.http.routers.app.tls.certresolver=letsencrypt"
        - "traefik.http.services.app.loadbalancer.server.port=3000"        

volumes:
  gitea_data:

networks:
  reverse_proxy_public:
    external: true

部署 Gitea:

bash
docker stack deploy -c gitea-stack.yml gitea

git.example.com 替换为所需的子域名。

部署后,访问 https://git.example.com 上的 Gitea 并完成初始设置。确保在设置中启用容器注册表。

配置 Yii 应用程序

更新 Makefile 配置

Yii 应用程序模板包含一个带有部署命令的 Makefile。更新项目中的 docker/.env 文件:

bash
STACK_NAME=myapp

#
# Production
#

PROD_HOST=app.example.com
PROD_SSH="ssh://user@your-server-ip"

IMAGE=git.example.com/username/myapp
IMAGE_TAG=latest

替换以下值:- STACK_NAME:应用程序堆栈的唯一名称 - PROD_HOST:应用程序可访问的域名 - PROD_SSH:到服务器的 SSH 连接字符串(格式:ssh://user@host)- IMAGE:注册表中容器镜像的完整路径 - IMAGE_TAG:镜像标签,通常为 latest 或版本号

配置生产环境

使用生产环境变量更新 docker/prod/.env

bash
APP_ENV=prod
YII_DEBUG=false
YII_ENV=prod

# Database configuration
DB_HOST=db
DB_NAME=myapp
DB_USER=myapp
DB_PASSWORD=secure_password_here

# Add other environment-specific variables

WARNING

切勿将敏感凭据提交到版本控制。对敏感值使用 docker/prod/override.env 并将其添加到 .gitignore

查看生产环境 Docker Compose 配置

使用 Traefik 时,你需要修改 Yii 应用程序的 docker/prod/compose.yml 以使用 Traefik 标签而不是 Caddy 标签:

yaml
services:
  app:
    image: ${IMAGE}:${IMAGE_TAG}
    networks:
      - reverse_proxy_public
    volumes:
      - runtime:/app/runtime
      - caddy_data:/data
      - caddy_config:/config
    env_file:
      - path: ./prod/.env
      - path: ./prod/override.env
        required: false\
    environment:
      CADDY_EXTRA_CONFIG: 'auto_https off'
    deploy:
      replicas: 2
      update_config:
        delay: 10s
        parallelism: 1
        order: start-first
        failure_action: rollback
        monitor: 10s
      rollback_config:
        parallelism: 0
        order: stop-first
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
        window: 120s
      labels:
        - "traefik.enable=true"
        - "traefik.http.routers.app.rule=Host(`${PROD_HOST:-app.example.com}`)"
        - "traefik.http.routers.app.entrypoints=websecure"
        - "traefik.http.routers.app.tls.certresolver=letsencrypt"
        - "traefik.http.services.app.loadbalancer.server.port=80"

此配置:

  • 运行 2 个副本以实现高可用性
  • 使用滚动更新策略,失败时自动回滚
  • 配置 labels 以在反向代理上自动启用 HTTPS
  • 禁用容器本身获取 HTTPS 证书 因为代理通过 HTTP 与容器通信。即 auto_https off

如果需要数据库,请将其添加到堆栈中:

yaml
services:
  app:
    # ... existing configuration ...
    
  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: myapp
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    volumes:
      - db_data:/var/lib/postgresql/data
    networks:
      - reverse_proxy_public
    deploy:
      placement:
        constraints:
          - node.role == manager
    secrets:
      - db_password

volumes:
  runtime:
  db_data:

secrets:
  db_password:
    external: true

在服务器上创建数据库 password secret:

bash
echo "your_secure_password" | docker secret create db_password -

构建并推送镜像

在本地机器上设置 Docker 登录

配置 Docker 以向容器注册表进行身份验证:

bash
docker login git.example.com

在提示时输入用户名和 password。

构建生产镜像

使用 Makefile 构建生产镜像:

bash
make prod-build

这将运行 Makefile 中定义的命令:

bash
docker build --file docker/Dockerfile --target prod --pull -t ${IMAGE}:${IMAGE_TAG} .

Dockerfile 使用多阶段构建:1. 在构建器阶段安装 Composer 依赖项 2. 创建仅包含必要文件的最小生产镜像 3. 以非 root 用户(www-data)身份运行

将镜像推送到注册表

将构建的镜像推送到容器注册表:

bash
make prod-push

这将执行:

bash
docker push ${IMAGE}:${IMAGE_TAG}

部署到 Docker Swarm

Configure SSH access

Set up SSH key-based authentication to your server:

bash
# Generate SSH key (if you don't have one)
ssh-keygen -t ed25519 -C "your_email@example.com"

# Copy the key to your server
ssh-copy-id user@your-server-ip

# Add the SSH host to your SSH config (~/.ssh/config)
cat >> ~/.ssh/config << EOF
Host docker-web
    HostName your-server-ip
    User user
    IdentityFile ~/.ssh/id_ed25519
EOF

设置 Docker 上下文

创建用于远程部署的 Docker 上下文:

bash
docker context create swarm-prod --docker "host=ssh://docker-web"

或者,配置 DOCKER_HOST 环境变量:

bash
export DOCKER_HOST=ssh://docker-web

部署应用程序

将应用程序堆栈部署到 Docker Swarm:

bash
make prod-deploy

这将执行:

bash
docker -H ${PROD_SSH} stack deploy --prune --detach=false --with-registry-auth -c docker/compose.yml -c docker/prod/compose.yml ${STACK_NAME}

--with-registry-auth 标志确保 Swarm 节点可以从私有注册表拉取镜像。

验证部署

检查服务状态:

bash
docker -H ssh://docker-web service ls
docker -H ssh://docker-web service ps ${STACK_NAME}_app

查看日志:

bash
docker -H ssh://docker-web service logs ${STACK_NAME}_app

监控和维护

查看服务日志

bash
# View all logs
docker -H ssh://docker-web service logs -f ${STACK_NAME}_app

# View logs from the last 100 lines
docker -H ssh://docker-web service logs --tail 100 ${STACK_NAME}_app

# View logs with timestamps
docker -H ssh://docker-web service logs -t ${STACK_NAME}_app

扩展应用程序

调整副本数量:

bash
docker -H ssh://docker-web service scale ${STACK_NAME}_app=3

或者更新 docker/prod/compose.yml 中的 replicas 值并重新部署。

资源限制

添加资源限制以防止容器消耗所有服务器资源。更新 docker/prod/compose.yml

yaml
services:
  app:
    # ... existing configuration ...
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 256M

安全注意事项

对敏感数据使用 Docker secrets

对于敏感信息,使用 Docker secrets 而不是环境变量:

bash
# 创建 secrets
echo "database_password" | docker secret create db_password -
echo "api_key" | docker secret create api_key -

更新 docker/prod/compose.yml

yaml
services:
  app:
    secrets:
      - db_password
      - api_key

secrets:
  db_password:
    external: true
  api_key:
    external: true

在应用程序中通过 /run/secrets/secret_name 访问 secrets。

设置防火墙

在服务器上配置 UFW(简单防火墙):

bash
# Allow SSH
sudo ufw allow 22/tcp

# Allow HTTP and HTTPS
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# Allow Docker Swarm ports (if you plan to add more nodes)
sudo ufw allow 2377/tcp
sudo ufw allow 7946/tcp
sudo ufw allow 7946/udp
sudo ufw allow 4789/udp

# Enable the firewall
sudo ufw enable

保持系统更新

定期更新服务器和 Docker:

bash
# 更新系统包
sudo apt-get update && sudo apt-get upgrade -y

# 更新 Docker 镜像
docker -H ssh://docker-web service update --image ${IMAGE}:${IMAGE_TAG} ${STACK_NAME}_app

故障排除

服务无法启动

检查服务事件和日志:

bash
docker -H ssh://docker-web service ps ${STACK_NAME}_app --no-trunc
docker -H ssh://docker-web service logs ${STACK_NAME}_app

常见问题:- 镜像拉取错误:使用 docker -H ssh://docker-web login 验证注册表身份验证 - 端口冲突:确保没有其他服务使用端口 80/443 - 资源约束:使用 docker -H ssh://docker-web node ls 检查可用资源

SSL 证书问题

如果 Traefik 无法获取证书:- 验证 DNS 是否指向服务器 - 检查端口 80 和 443 是否可从互联网访问 - 确保 Let's Encrypt 配置中的电子邮件有效 - 检查日志:docker -H ssh://docker-web service logs traefik_traefik

容器注册表连接问题

测试注册表连接:

bash
# 从本地机器
docker pull git.example.com/username/myapp:latest

# 从服务器
docker -H ssh://docker-web pull git.example.com/username/myapp:latest

总结

你已成功将 Yii 应用程序部署到 Docker Swarm,包括:- 容器注册表(Forgejo 或 Gitea)- 通过 Traefik 自动启用 HTTPS - 通过滚动更新实现零停机部署 - 通过多个副本实现高可用性

Makefile 命令简化了部署工作流程:- make prod-build - 构建生产镜像 - make prod-push - 推送到注册表 - make prod-deploy - 部署到 Docker Swarm

有关更多信息,请参阅:- Yii 应用程序模板 - Docker Swarm 文档 - Traefik 文档