第 21 章:Docker 部署
第 21 章:Docker 部署
“在我机器上能跑!” —— Docker 终结了这句话。
21.1 Docker 基础
21.1.1 为什么使用 Docker
| 优势 | 说明 |
|---|
| 环境一致性 | 开发、测试、生产环境完全一致 |
| 隔离性 | 应用互不干扰 |
| 可移植性 | 任何支持 Docker 的机器都能运行 |
| 版本控制 | 镜像版本化管理 |
| 扩展性 | 容器编排轻松实现水平扩展 |
21.1.2 Docker 核心概念
Dockerfile → 构建镜像的"菜谱"
Image (镜像) → 只读模板,包含应用和依赖
Container (容器) → 镜像的运行实例
Volume (卷) → 持久化存储
Network (网络) → 容器间通信
21.2 基本 Dockerfile
21.2.1 简单的 Ruby 应用
# Dockerfile
FROM ruby:3.3-slim
# 安装系统依赖
RUN apt-get update -qq && \
apt-get install -y --no-install-recommends \
build-essential \
libpq-dev \
git \
curl \
&& rm -rf /var/lib/apt/lists/*
# 设置工作目录
WORKDIR /app
# 先复制依赖文件(利用缓存)
COPY Gemfile Gemfile.lock ./
# 安装依赖
RUN bundle config set --local without 'development test' && \
bundle install --jobs 4 --retry 3
# 复制应用代码
COPY . .
# 暴露端口
EXPOSE 3000
# 启动命令
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]
21.2.2 .dockerignore
# .dockerignore
.git
.github
.env
.env.*
*.gem
*.rbc
.bundle/
vendor/bundle
log/*
tmp/*
tmp/**/*
coverage/*
spec/*
test/*
.DS_Store
.idea/
.vscode/
node_modules/
21.3 多阶段构建
21.3.1 Rails 应用多阶段构建
# ========== 阶段 1:构建 ==========
FROM ruby:3.3-slim AS builder
# 安装构建依赖
RUN apt-get update -qq && \
apt-get install -y --no-install-recommends \
build-essential \
libpq-dev \
git \
nodejs \
npm \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# 安装 JavaScript 依赖
COPY package.json package-lock.json ./
RUN npm ci --production
# 安装 Ruby 依赖
COPY Gemfile Gemfile.lock ./
RUN bundle config set --local deployment true && \
bundle config set --local without 'development test' && \
bundle install --jobs 4 --retry 3 && \
bundle exec bootsnap precompile --gemfile
# 复制代码并预编译资源
COPY . .
RUN SECRET_KEY_BASE=placeholder bundle exec rails assets:precompile
# ========== 阶段 2:运行 ==========
FROM ruby:3.3-slim AS runtime
# 安装运行时依赖
RUN apt-get update -qq && \
apt-get install -y --no-install-recommends \
libpq5 \
curl \
&& rm -rf /var/lib/apt/lists/*
# 创建非 root 用户
RUN groupadd -r appuser && useradd -r -g appuser appuser
WORKDIR /app
# 从构建阶段复制文件
COPY --from=builder --chown=appuser:appuser /app /app
# 切换用户
USER appuser
EXPOSE 3000
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]
21.3.2 多阶段构建的优势
| 特性 | 单阶段 | 多阶段 |
|---|
| 镜像大小 | 大(包含编译工具) | 小(只有运行时) |
| 安全性 | 包含开发工具 | 最小攻击面 |
| 构建缓存 | 有限 | 高效分层缓存 |
| 推荐度 | 快速原型 | 生产环境 |
21.4 Bundler 缓存优化
21.4.1 利用 Docker 层缓存
# ❌ 每次代码变更都重新安装依赖
COPY . .
RUN bundle install
# ✅ 先复制 Gemfile,利用缓存
COPY Gemfile Gemfile.lock ./
RUN bundle install
COPY . .
21.4.2 完整的缓存策略
FROM ruby:3.3-slim AS builder
RUN apt-get update -qq && \
apt-get install -y --no-install-recommends build-essential libpq-dev
WORKDIR /app
# 第 1 层:Gemfile 缓存
COPY Gemfile Gemfile.lock ./
RUN bundle config set --local deployment true && \
bundle install --jobs 4 --retry 3
# 第 2 层:应用代码
COPY . .
# 清理缓存
RUN rm -rf vendor/bundle/ruby/*/cache/*.gem && \
find vendor/bundle/ruby/*/gems/ -name "*.c" -delete && \
find vendor/bundle/ruby/*/gems/ -name "*.o" -delete
21.5 生产配置
21.5.1 Puma 配置
# config/puma.rb
workers ENV.fetch("WEB_CONCURRENCY") { 2 }
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
threads threads_count, threads_count
preload_app!
port ENV.fetch("PORT") { 3000 }
environment ENV.fetch("RAILS_ENV") { "production" }
plugin :tmp_restart
on_worker_boot do
ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
end
before_fork do
ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord)
end
21.5.2 环境变量
# .env.production
RAILS_ENV=production
RAILS_LOG_TO_STDOUT=true
RAILS_SERVE_STATIC_FILES=true
SECRET_KEY_BASE=your_secret_key_here
DATABASE_URL=postgresql://user:pass@db:5432/myapp
WEB_CONCURRENCY=2
RAILS_MAX_THREADS=5
21.5.3 健康检查
# config/routes.rb
Rails.application.routes.draw do
get "/health", to: proc { [200, { "Content-Type" => "application/json" },
[{ status: "ok", version: ENV.fetch("APP_VERSION", "unknown") }.to_json]] }
end
# Dockerfile 中添加健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
21.6 Docker Compose
21.6.1 完整的 docker-compose.yml
# docker-compose.yml
version: '3.8'
services:
web:
build:
context: .
dockerfile: Dockerfile
ports:
- "3000:3000"
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
environment:
- RAILS_ENV=production
- DATABASE_URL=postgresql://postgres:password@db:5432/myapp
- REDIS_URL=redis://redis:6379/0
- SECRET_KEY_BASE=${SECRET_KEY_BASE}
volumes:
- rails_storage:/app/storage
restart: unless-stopped
db:
image: postgres:16-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: password
POSTGRES_DB: myapp
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
worker:
build:
context: .
dockerfile: Dockerfile
command: bundle exec sidekiq
depends_on:
- db
- redis
environment:
- RAILS_ENV=production
- DATABASE_URL=postgresql://postgres:password@db:5432/myapp
- REDIS_URL=redis://redis:6379/0
restart: unless-stopped
volumes:
postgres_data:
redis_data:
rails_storage:
21.6.2 常用命令
# 构建并启动
docker-compose up -d --build
# 查看日志
docker-compose logs -f web
docker-compose logs -f --tail 100
# 进入容器
docker-compose exec web bash
docker-compose exec web rails console
docker-compose exec web rails db:migrate
# 停止服务
docker-compose down
# 停止并删除数据
docker-compose down -v
# 查看状态
docker-compose ps
21.7 CI/CD 集成
21.7.1 GitHub Actions
# .github/workflows/deploy.yml
name: Build and Deploy
on:
push:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- run: bundle exec rspec
build:
needs: test
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
21.8 安全最佳实践
21.8.1 镜像安全
# 1. 使用官方基础镜像
FROM ruby:3.3-slim
# 2. 创建非 root 用户
RUN groupadd -r appuser && useradd -r -g appuser appuser
# 3. 设置正确的文件权限
COPY --chown=appuser:appuser . /app
# 4. 使用 USER 指令
USER appuser
# 5. 不要暴露不必要的端口
EXPOSE 3000
# 6. 使用 HEALTHCHECK
HEALTHCHECK CMD curl -f http://localhost:3000/health || exit 1
21.8.2 Docker 安全扫描
# 使用 Trivy 扫描漏洞
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy image myapp:latest
# 使用 Docker Scout
docker scout cves myapp:latest
21.9 动手练习
- 为现有应用创建 Dockerfile
# 为你的 Ruby 项目创建生产级 Dockerfile
# 包含多阶段构建和健康检查
- 配置 Docker Compose
# 创建完整的 docker-compose.yml
# 包含 Web、数据库、Redis、后台任务
- 优化镜像大小
# 比较不同构建策略的镜像大小
# 目标:小于 200MB
21.10 本章小结
| 要点 | 说明 |
|---|
| Dockerfile | 构建镜像的指令文件 |
| 多阶段构建 | 减小镜像大小,提高安全性 |
| 缓存优化 | 合理分层利用 Docker 缓存 |
| Docker Compose | 多容器编排 |
| 安全 | 非 root 用户、最小镜像、漏洞扫描 |
| CI/CD | GitHub Actions 自动构建部署 |
📖 扩展阅读
上一章:← 第 20 章:性能优化
下一章:第 22 章:最佳实践 →