容器化部署
使用 Docker 容器化部署 Viswoole 应用可以实现环境一致性、快速扩缩容和简化运维。本文档提供完整的容器化部署方案。
Dockerfile
基础镜像选择
dockerfile
# 基于 PHP 8.3 + Swoole 的官方镜像
FROM phpswoole/swoole:php8.3-alpine
# 设置工作目录
WORKDIR /var/www/app
# 安装系统依赖
RUN apk add --no-cache
linux-headers
zlib-dev
libzip-dev
oniguruma-dev
&& docker-php-ext-install -j$(nproc)
pdo_mysql
mysqli
opcache
bcmath
zip
mbstring
pcntl
&& pecl install redis-6.0.2
&& docker-php-ext-enable redis
# 复制 composer 文件并安装依赖
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-interaction --optimize-autoloader
# 复制应用代码
COPY . .
# 创建运行时目录
RUN mkdir -p runtime/cache runtime/logs &&
chown -R www-data:www-data /var/www/app
# 设置环境变量
ENV APP_ENV=production
ENV APP_DEBUG=false
# 暴露端口
EXPOSE 9501
# 启动命令
CMD ["php", "viswoole", "server:start"]多阶段构建(减小镜像体积)
dockerfile
# ============================================
# 阶段一: Composer 依赖安装
# ============================================
FROM composer:2 AS composer-stage
WORKDIR /build
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader
# ============================================
# 阶段二:最终镜像
# ============================================
FROM phpswoole/swoole:php8.3-alpine AS production
# 安装运行时依赖
RUN apk add --no-cache
libzip-dev
curl
tzdata
&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
&& echo "Asia/Shanghai" > /etc/timezone
&& docker-php-ext-install -j$(nproc)
pdo_mysql
opcache
zip
bcmath
&& pecl install redis-6.0.2
&& docker-php-ext-enable redis
# 从阶段一复制 vendor 目录
COPY --from=composer-stage /build/vendor ./vendor
# 复制应用代码
WORKDIR /var/www/app
COPY . .
# 清理不需要的文件
RUN rm -rf .git .github .env.example tests phpunit.xml docker-compose*.yml
&& chmod -R 755 /var/www/app
&& chown -R www-data:www-data /var/www/app
USER www-data
ENV APP_ENV=production
ENV APP_DEBUG=false
EXPOSE 9501
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3
CMD curl -f http://localhost:9501/health || exit 1
CMD ["php", "viswoole", "server:start"]Docker Compose 编排
基础编排(单机部署)
yaml
# docker-compose.yml
services:
# Viswoole 应用
app:
build:
context: .
dockerfile: Dockerfile
container_name: viswoole-app
restart: unless-stopped
ports:
- '#123;APP_PORT:-9501}:9501'
environment:
- APP_ENV=#123;APP_ENV:-production}
- APP_DEBUG=#123;APP_DEBUG:-false}
- DATABASE_HOST=db
- DATABASE_PORT=3306
- DATABASE_NAME=#123;DATABASE_NAME:-viswoole}
- DATABASE_USER=#123;DATABASE_USER:-root}
- DATABASE_PASSWORD=#123;DATABASE_PASSWORD:-secret}
- REDIS_HOST=redis
- REDIS_PORT=6379
- REDIS_PASSWORD=#123;REDIS_PASSWORD:-}
- CACHE_STORE=redis
- WORKER_NUM=#123;WORKER_NUM:-4}
- TASK_WORKER_NUM=#123;TASK_WORKER_NUM:-2}
- SWOOLE_DAEMONIZE=false
- LOG_FILE=/var/log/app/swoole.log
volumes:
- ./runtime:/var/www/app/runtime:rw
- app-logs:/var/log/app:rw
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
networks:
- app-network
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:9501/health']
interval: 30s
timeout: 5s
retries: 3
deploy:
resources:
limits:
memory: 512M
cpus: '2'
reservations:
memory: 256M
cpus: '1'
# MySQL 数据库
db:
image: mysql:8.0
container_name: viswoole-db
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: #123;DATABASE_ROOT_PASSWORD:-root_secret}
MYSQL_DATABASE: #123;DATABASE_NAME:-viswoole}
MYSQL_USER: #123;DATABASE_USER:-app_user}
MYSQL_PASSWORD: #123;DATABASE_PASSWORD:-secret}
MYSQL_CHARACTER_SET_SERVER: utf8mb4
MYSQL_COLLATION_SERVER: utf8mb4_unicode_ci
ports:
- '#123;DATABASE_PORT:-3306}:3306'
volumes:
- mysql_data:/var/lib/mysql:rw
- ./deploy/mysql/conf.d:/etc/mysql/conf.d:ro
- ./deploy/mysql/init.d:/docker-entrypoint-initdb.d:ro
networks:
- app-network
healthcheck:
test:
[
'CMD',
'mysqladmin',
'ping',
'-h',
'localhost',
'-u',
'root',
'-p#123;DATABASE_ROOT_PASSWORD:-root_secret}'
]
interval: 10s
timeout: 5s
retries: 5
# Redis 缓存
redis:
image: redis:7-alpine
container_name: viswoole-redis
restart: unless-stopped
command: redis-server --requirepass #123;REDIS_PASSWORD:-} --maxmemory 256mb --maxmemory-policy allkeys-lru
ports:
- '#123;REDIS_PORT:-6379}:6379'
volumes:
- redis_data:/data:rw
networks:
- app-network
healthcheck:
test: ['CMD', 'redis-cli', '-a', '#123;REDIS_PASSWORD:-}', 'ping']
interval: 10s
timeout: 5s
retries: 5
# Nginx 反向代理(可选)
nginx:
image: nginx:alpine
container_name: viswoole-nginx
restart: unless-stopped
ports:
- '80:80'
- '443:443'
volumes:
- ./deploy/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./deploy/nginx/conf.d:/etc/nginx/conf.d:ro
- ./deploy/nginx/ssl:/etc/nginx/ssl:ro
- nginx_logs:/var/log/nginx:rw
depends_on:
- app
networks:
- app-network
volumes:
mysql_data:
driver: local
redis_data:
driver: local
app-logs:
driver: local
nginx_logs:
driver: local
networks:
app-network:
driver: bridge多实例水平扩展
yaml
# docker-compose.scale.yml
services:
app:
build: .
environment:
- SERVER_PORT=9501
deploy:
replicas: 4 # 运行 4 个实例
resources:
limits:
cpus: '1'
memory: 512M
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
update_config:
parallelism: 2
delay: 10s
failure_action: rollback
# 使用 HAProxy 或 Nginx 做负载均衡
loadbalancer:
image: haproxy:alpine
ports:
- '80:80'
volumes:
- ./deploy/haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
depends_on:
- appKubernetes 部署
Deployment 配置
yaml
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: viswoole-app
labels:
app: viswoole
spec:
replicas: 3
selector:
matchLabels:
app: viswoole
template:
metadata:
labels:
app: viswoole
spec:
containers:
- name: viswoole
image: your-registry/viswoole-app:latest
ports:
- containerPort: 9501
env:
- name: APP_ENV
value: 'production'
- name: APP_DEBUG
value: 'false'
- name: DATABASE_HOST
valueFrom:
secretKeyRef:
name: app-secrets
key: db-host
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: app-secrets
key: db-password
- name: REDIS_HOST
value: 'redis-service'
- name: WORKER_NUM
value: '4'
- name: SWOOLE_DAEMONIZE
value: 'false'
resources:
requests:
memory: '256Mi'
cpu: '250m'
limits:
memory: '512Mi'
cpu: '500m'
livenessProbe:
httpGet:
path: /health
port: 9501
initialDelaySeconds: 15
periodSeconds: 20
readinessProbe:
httpGet:
path: /health
port: 9501
initialDelaySeconds: 5
periodSeconds: 10
volumeMounts:
- name: app-logs
mountPath: /var/log/app
volumes:
- name: app-logs
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: viswoole-service
spec:
selector:
app: viswoole
ports:
- protocol: TCP
port: 9501
targetPort: 9501
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: viswoole-ingress
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: '20m'
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: viswoole-service
port:
number: 9501ConfigMap 与 Secret
yaml
# k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
APP_NAME: 'Viswoole Production'
APP_URL: 'https://api.example.com'
CACHE_STORE: 'redis'
WORKER_NUM: '4'
---
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
stringData:
db-host: 'mysql-service'
db-database: 'production_db'
db-username: 'app_user'
db-password: '<strong_password>'
redis-password: '<redis_password>'
app-key: '<generated_app_key>'CI/CD 流水线
GitHub Actions 示例
yaml
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
extensions: swoole, redis, pdo_mysql, bcmath
tools: composer:v2
- name: Install dependencies
run: composer install --no-dev --optimize-autoloader
- name: Run tests
run: vendor/bin/phpunit
- name: Build Docker image
run: docker build -t your-registry/viswoole:#123;{ github.sha }} .
- name: Push to Registry
run: |
echo "#123;{ secrets.REGISTRY_PASSWORD }}" | docker login your-registry -u "#123;{ secrets.REGISTRY_USERNAME }}" --password-stdin
docker push your-registry/viswoole:#123;{ github.sha }}
- name: Deploy to Server
uses: appleboy/ssh-action@v1
with:
host: #123;{ secrets.SERVER_HOST }}
username: #123;{ secrets.SERVER_USER }}
key: #123;{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /opt/viswoole
docker compose pull
docker compose up -d --remove-orphans
docker image prune -f运维操作
常用 Docker 命令
bash
# 构建镜像
docker build -t viswoole-app:v1.0 .
# 启动服务
docker compose up -d
# 查看日志
docker compose logs -f app
# 进入容器
docker compose exec app bash
# 重启服务
docker compose restart app
# 平滑重载(零停机)
docker compose exec app php viswoole server:reload
# 扩缩容
docker compose up -d --scale app=4
# 查看资源使用
docker stats viswoole-app
# 清理无用资源
docker system prune -a监控与排查
bash
# 查看容器状态
docker compose ps
# 查看健康检查
docker inspect --format='{{json .State.Health}}' viswoole-app
# 查看 Swoole 进程信息
docker compose exec app php --ri swoole
# 查看实时日志
docker compose logs -f --tail=100 app
# 查看资源统计
docker stats --no-stream最佳实践
1. 镜像优化
- 使用多阶段构建减少镜像体积
- 利用 Docker 层缓存(先 COPY composer.json 再 COPY .)
- 生产镜像中排除
.git、tests、.env.example等非必需文件 - 使用 Alpine 基础镜像减小体积
2. 安全实践
- 不要在镜像中硬编码敏感信息,使用环境变量或 Secrets
- 以非 root 用户运行容器
- 只暴露必要的端口
- 定期更新基础镜像以修复安全漏洞
- 使用
.dockerignore排除敏感文件
dockerfile
# .dockerignore
.git
.github
.env
.env.*
tests/
phpunit.xml
Dockerfile*
docker-compose*.yml
README.md
docs/
.vscode/
.idea/3. 健康检查
始终为容器配置健康检查端点:
php
// app/Controller/HealthController.php
class HealthController extends Controller
{
public function index(): array
{
// 检查关键依赖
$checks = [
'database' => $this->checkDatabase(),
'redis' => $this->checkRedis(),
];
$healthy = !in_array(false, array_values($checks));
return json([
'status' => $healthy ? 'ok' : 'degraded',
'checks' => $checks,
'timestamp' => time(),
], $healthy ? 200 : 503);
}
}4. 日志收集
将容器日志统一输出到 stdout/stderr,便于集中收集:
bash
# docker-compose.yml 中的日志配置
services:
app:
logging:
driver: json-file
options:
max-size: "50m"
max-file: "5"