Docker是2016年最流行的容器化技术,由Docker公司(原dotCloud)开发。它让应用部署变得简单、高效、可移植,彻底改变了软件开发和运维的方式。"一次构建,到处运行"(Build once, run anywhere),是Docker的核心理念。

2016年,Docker已经从一个新兴技术变成了业界标准。越来越多的公司开始使用Docker进行容器化部署,微服务架构也因为Docker的普及而变得更加容易实现。Docker Compose、Docker Swarm、Kubernetes等容器编排工具也在快速发展。

作为一个后端开发者,我在2016年开始在项目中使用Docker进行容器化部署,从开发环境到生产环境,从单容器到多容器编排,积累了很多实战经验。今天就来系统地讲解Docker容器化部署实战,从Dockerfile编写到生产环境部署,从镜像优化到安全加固,帮助你将应用快速、可靠地部署到生产环境。

一、Docker核心概念回顾

在开始实战之前,先回顾一下Docker的核心概念。

1. 镜像(Image)

镜像是Docker容器的只读模板,包含了运行应用所需的一切:操作系统、运行时、代码、依赖、环境变量、配置文件等。镜像由多个层(Layer)组成,每个层对应Dockerfile中的一条指令。

镜像可以基于其他镜像构建,比如一个Node.js应用的镜像可以基于官方的node镜像构建。镜像存储在Docker Registry中(如Docker Hub),可以拉取和推送。

2. 容器(Container)

容器是镜像的运行实例,是一个隔离的运行环境。容器共享宿主机的内核,但是有自己的文件系统、网络、进程空间。容器是轻量级的,启动速度快(秒级),资源消耗小。

容器可以被创建、启动、停止、删除、暂停。容器的状态是临时的,删除容器后,容器内的数据也会丢失(除非使用数据卷)。

3. 数据卷(Volume)

数据卷是Docker中用于持久化数据的机制。数据卷可以将宿主机的目录或文件挂载到容器中,容器删除后,数据卷中的数据不会丢失。

数据卷的特点:

  • 数据卷可以在容器之间共享和重用
  • 对数据卷的修改会直接生效
  • 数据卷的生命周期独立于容器
  • 数据卷可以挂载到多个容器

4. 网络(Network)

Docker提供了网络功能,让容器之间可以通信。Docker默认有三种网络模式:

  • bridge(默认):容器连接到docker0网桥,通过NAT访问外网
  • host:容器共享宿主机的网络栈
  • none:容器没有网络

用户也可以创建自定义网络,让特定的容器在同一个网络中通信,实现网络隔离。

5. Dockerfile

Dockerfile是一个文本文件,包含了一系列构建镜像的指令。通过Dockerfile,可以自动化地构建镜像,实现镜像构建的可重复性和版本控制。

二、Dockerfile编写最佳实践

Dockerfile是Docker镜像构建的核心,编写好的Dockerfile能让镜像更小、构建更快、更安全。

1. 基础镜像选择

选择合适的基础镜像,是优化镜像的第一步。

# 不好:使用完整的Ubuntu镜像,体积大
FROM ubuntu:16.04

# 好:使用轻量级的Alpine镜像,体积小(约5MB)
FROM alpine:3.4

# 更好:使用官方的运行时镜像,已经预装了运行时
FROM node:6-alpine
FROM python:3.5-alpine
FROM php:7.0-fpm-alpine

基础镜像选择原则:

  • 优先使用官方镜像,安全可靠
  • 优先使用alpine版本,体积小,安全
  • 不要使用过旧的镜像版本,及时更新
  • 不要使用latest标签,使用具体版本号,保证可重复性

2. 减少镜像层数

Dockerfile中的每条指令(RUN、COPY、ADD等)都会创建一个新的层。层越多,镜像越大,构建越慢。应该尽量减少层数。

# 不好:多个RUN指令,创建多个层
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y git
RUN apt-get clean

# 好:合并RUN指令,用&&连接,只创建一个层
RUN apt-get update && apt-get install -y \
    curl \
    git \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

3. 利用构建缓存

Docker构建时会缓存每一层的结果,如果指令和上下文没有变化,就会使用缓存。合理安排指令顺序,可以充分利用缓存,加快构建速度。

# 不好:先复制代码,再安装依赖。代码一变,依赖缓存就失效
FROM node:6-alpine
WORKDIR /app
COPY . .
RUN npm install
EXPOSE 3000
CMD ["npm", "start"]

# 好:先复制依赖文件,安装依赖,再复制代码。依赖文件不变,缓存就有效
FROM node:6-alpine
WORKDIR /app
COPY package.json .
RUN npm install --production
COPY . .
EXPOSE 3000
CMD ["npm", "start"]

原则:将变化频率低的指令放在前面,变化频率高的指令放在后面。这样可以充分利用构建缓存。

4. 使用.dockerignore

.dockerignore文件可以排除不需要的文件和目录,减少构建上下文的大小,加快构建速度,同时避免将敏感文件(如.env、密钥)打包进镜像。

# .dockerignore
node_modules
npm-debug.log
.git
.gitignore
.env
.env.local
*.log
.DS_Store
dist
build
coverage
.idea
.vscode

5. 多阶段构建(Multi-stage Build)

多阶段构建是Docker 17.05引入的特性(2016年还没有,但是可以提一下作为未来方向)。2016年可以使用构建容器的方式来实现类似效果。

2016年的方式:使用一个容器构建,然后将构建产物复制到运行时镜像。

# 构建容器
docker run --rm -v $(pwd):/app -w /app node:6 npm install --production

# 构建运行时镜像
docker build -t myapp .
# 运行时镜像,只包含运行时和构建产物
FROM node:6-alpine
WORKDIR /app
COPY node_modules ./node_modules
COPY . .
EXPOSE 3000
CMD ["npm", "start"]

6. 不要在镜像中存储敏感信息

不要将密码、密钥、证书等敏感信息写入Dockerfile或镜像中。应该使用环境变量、Docker secrets或配置文件挂载的方式传递敏感信息。

# 不好:硬编码密码
ENV DB_PASSWORD=mysecretpassword

# 好:使用环境变量,运行时传入
ENV DB_PASSWORD=
# 运行时:docker run -e DB_PASSWORD=xxx myapp

7. 使用非root用户运行容器

默认情况下,Docker容器以root用户运行,这存在安全风险。应该在Dockerfile中创建非root用户,并使用该用户运行应用。

FROM node:6-alpine
WORKDIR /app
COPY package.json .
RUN npm install --production
COPY . .

# 创建非root用户
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

EXPOSE 3000
CMD ["npm", "start"]

8. 健康检查(HEALTHCHECK)

Docker 1.12+支持HEALTHCHECK指令,可以检查容器的健康状态。2016年Docker 1.12已经发布,可以使用。

HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

三、镜像优化技巧

1. 减小镜像体积

  • 使用alpine基础镜像
  • 合并RUN指令,清理包管理器缓存
  • 只安装必要的依赖,不安装开发依赖
  • 使用多阶段构建,只将运行时需要的文件复制到最终镜像
  • 使用.dockerignore排除不需要的文件

2. 加快构建速度

  • 合理安排Dockerfile指令顺序,利用构建缓存
  • 使用.dockerignore减少构建上下文
  • 使用国内镜像源(如阿里云、DaoCloud)加速基础镜像拉取
  • 并行构建独立的镜像层

3. 镜像安全

  • 使用官方或可信的基础镜像
  • 定期更新基础镜像,修复安全漏洞
  • 不要在镜像中存储敏感信息
  • 使用非root用户运行容器
  • 扫描镜像漏洞(使用Docker Bench for Security、Clair等工具)

四、Docker Compose多容器编排

Docker Compose是Docker官方的多容器编排工具,通过一个YAML文件定义和运行多个容器。2016年,Docker Compose已经比较成熟,是开发环境和小型生产环境的首选。

1. 安装Docker Compose

# Linux安装
curl -L https://github.com/docker/compose/releases/download/1.8.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

# 验证安装
docker-compose --version

2. docker-compose.yml示例

一个典型的Web应用(Nginx + PHP-FPM + MySQL + Redis)的docker-compose.yml:

version: '2'

services:
  nginx:
    image: nginx:1.11-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d
      - ./nginx/ssl:/etc/nginx/ssl
      - ./www:/var/www/html
    depends_on:
      - php
      - mysql
      - redis
    networks:
      - webnet
    restart: always

  php:
    build: ./php
    volumes:
      - ./www:/var/www/html
      - ./php/php.ini:/usr/local/etc/php/php.ini
    depends_on:
      - mysql
      - redis
    networks:
      - webnet
    restart: always

  mysql:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: rootpassword
      MYSQL_DATABASE: mydb
      MYSQL_USER: myuser
      MYSQL_PASSWORD: mypassword
    volumes:
      - mysql_data:/var/lib/mysql
      - ./mysql/init:/docker-entrypoint-initdb.d
    ports:
      - "3306:3306"
    networks:
      - webnet
    restart: always

  redis:
    image: redis:3.2-alpine
    volumes:
      - redis_data:/data
      - ./redis/redis.conf:/usr/local/etc/redis/redis.conf
    command: redis-server /usr/local/etc/redis/redis.conf
    ports:
      - "6379:6379"
    networks:
      - webnet
    restart: always

volumes:
  mysql_data:
  redis_data:

networks:
  webnet:
    driver: bridge

3. Docker Compose常用命令

# 启动所有服务(后台运行)
docker-compose up -d

# 启动指定服务
docker-compose up -d nginx

# 停止所有服务
docker-compose down

# 停止但不删除容器
docker-compose stop

# 启动已停止的服务
docker-compose start

# 重启服务
docker-compose restart

# 查看服务状态
docker-compose ps

# 查看服务日志
docker-compose logs
docker-compose logs -f nginx  # 跟踪nginx日志

# 构建镜像
docker-compose build
docker-compose build --no-cache  # 不使用缓存

# 进入容器
docker-compose exec php bash

# 运行一次性命令
docker-compose run --rm php php -v

# 缩放服务(运行多个实例)
docker-compose scale php=3

4. Docker Compose配置技巧

  • 环境变量:使用.env文件存储环境变量,docker-compose会自动读取
  • 配置覆盖:使用docker-compose.override.yml覆盖默认配置,区分开发和生产环境
  • 健康检查:在服务中配置healthcheck,确保服务健康
  • 资源限制:使用mem_limit、cpuset等配置限制容器资源
  • 日志配置:使用logging配置日志驱动和大小,避免日志占满磁盘

五、数据卷与网络

1. 数据卷管理

# 创建数据卷
docker volume create mydata

# 查看数据卷
docker volume ls

# 查看数据卷详情
docker volume inspect mydata

# 删除数据卷
docker volume rm mydata

# 删除未使用的数据卷
docker volume prune

数据卷使用场景:

  • 数据库数据持久化(MySQL、PostgreSQL等)
  • 日志文件持久化
  • 配置文件挂载
  • 多个容器之间共享数据

2. 网络管理

# 创建网络
docker network create mynetwork

# 查看网络
docker network ls

# 查看网络详情
docker network inspect mynetwork

# 将容器连接到网络
docker network connect mynetwork mycontainer

# 将容器从网络断开
docker network disconnect mynetwork mycontainer

# 删除网络
docker network rm mynetwork

# 删除未使用的网络
docker network prune

网络使用场景:

  • 容器之间通信(同一网络中的容器可以通过容器名互相访问)
  • 网络隔离(不同网络中的容器默认不能通信)
  • 服务发现(Docker内置DNS,可以通过容器名解析IP)

六、生产环境部署

1. 生产环境注意事项

  • 不要使用latest标签:使用具体版本号,保证可重复性和回滚能力
  • 不要在容器中存储数据:使用数据卷持久化数据
  • 配置健康检查:确保服务健康,自动重启不健康的容器
  • 限制资源使用:限制容器的CPU和内存,避免单个容器占用过多资源
  • 配置日志:使用日志驱动,集中管理日志,避免日志占满磁盘
  • 安全加固:使用非root用户,关闭不需要的端口,定期更新镜像
  • 备份数据:定期备份数据库和重要数据
  • 监控告警:监控容器和服务的运行状态,设置告警

2. 容器重启策略

services:
  nginx:
    image: nginx:1.11-alpine
    restart: always  # 总是重启(除非手动停止)
    # restart: on-failure  # 失败时重启
    # restart: unless-stopped  # 除非手动停止,否则总是重启

3. 资源限制

services:
  php:
    image: php:7.0-fpm-alpine
    mem_limit: 512m  # 内存限制
    cpuset: "0,1"    # CPU核心限制
    # Docker Compose v2使用cpu_shares、mem_limit
    # Docker Compose v3使用deploy.resources(仅Swarm模式)

4. 日志配置

services:
  nginx:
    image: nginx:1.11-alpine
    logging:
      driver: json-file
      options:
        max-size: "10m"   # 单个日志文件最大10MB
        max-file: "3"      # 最多保留3个日志文件

也可以使用syslog、fluentd等日志驱动,将日志集中管理。

5. 反向代理与负载均衡

在生产环境中,通常使用Nginx或HAProxy作为反向代理,将请求分发到多个容器实例。

services:
  nginx:
    image: nginx:1.11-alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - app
    networks:
      - webnet

  app:
    image: myapp:1.0.0
    expose:
      - "3000"
    networks:
      - webnet
    deploy:
      replicas: 3  # Swarm模式下运行3个实例

Nginx配置:

upstream app_backend {
    server app:3000;  # Docker DNS会解析到所有app容器
}

server {
    listen 80;
    location / {
        proxy_pass http://app_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

七、日志与监控

1. 日志管理

# 查看容器日志
docker logs mycontainer
docker logs -f mycontainer  # 跟踪日志
docker logs --tail 100 mycontainer  # 查看最后100行

# 查看Docker Compose服务日志
docker-compose logs
docker-compose logs -f nginx

日志最佳实践:

  • 应用日志输出到stdout/stderr,由Docker收集
  • 使用日志驱动(json-file、syslog、fluentd等)集中管理日志
  • 配置日志轮转,避免日志占满磁盘
  • 使用ELK(Elasticsearch + Logstash + Kibana)或EFK进行日志分析

2. 容器监控

# 查看容器资源使用情况
docker stats

# 查看所有容器状态
docker ps -a

# 查看容器详情
docker inspect mycontainer

# 查看容器进程
docker top mycontainer

# 查看容器端口映射
docker port mycontainer

监控工具:

  • cAdvisor:Google开源的容器监控工具
  • Prometheus + Grafana:主流的监控和可视化方案
  • Datadog:商业监控服务
  • Weave Scope:容器网络和拓扑可视化

八、安全加固

1. Docker安全最佳实践

  • 保持Docker版本最新,及时修复安全漏洞
  • 使用官方或可信的基础镜像
  • 定期扫描镜像漏洞(Docker Bench for Security、Clair)
  • 使用非root用户运行容器
  • 不要在镜像中存储敏感信息
  • 限制容器的系统调用(seccomp)和能力(capabilities)
  • 使用只读文件系统(read-only)
  • 不要使用--privileged模式,除非必要
  • 配置容器资源限制,防止DoS攻击
  • 隔离容器网络,使用自定义网络
  • 定期清理不需要的镜像、容器、数据卷

2. Docker Bench for Security

Docker Bench for Security是Docker官方的安全审计脚本,可以检查Docker部署的安全性。

git clone https://github.com/docker/docker-bench-security.git
cd docker-bench-security
sudo sh docker-bench-security.sh

九、CI/CD集成

Docker可以很好地与CI/CD工具集成,实现自动化构建、测试和部署。

1. 典型CI/CD流程

  1. 开发者提交代码到Git仓库
  2. CI服务器(Jenkins、GitLab CI、Travis CI等)检测到代码变更
  3. CI服务器拉取代码,运行Dockerfile构建镜像
  4. 运行测试(单元测试、集成测试)
  5. 测试通过后,将镜像推送到Docker Registry
  6. 部署服务器拉取最新镜像,更新容器(滚动更新)

2. Jenkins Pipeline示例

pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                sh 'docker build -t myapp:${BUILD_NUMBER} .'
            }
        }
        stage('Test') {
            steps {
                sh 'docker run --rm myapp:${BUILD_NUMBER} npm test'
            }
        }
        stage('Push') {
            steps {
                sh 'docker tag myapp:${BUILD_NUMBER} registry.example.com/myapp:${BUILD_NUMBER}'
                sh 'docker push registry.example.com/myapp:${BUILD_NUMBER}'
            }
        }
        stage('Deploy') {
            steps {
                sh 'ssh deploy@server "docker pull registry.example.com/myapp:${BUILD_NUMBER} && docker-compose up -d"'
            }
        }
    }
}

十、常见问题与排错

1. 容器启动后立即退出

  • 检查容器的CMD/ENTRYPOINT是否正确
  • 检查应用是否在前台运行(Docker容器需要前台进程)
  • 查看容器日志:docker logs mycontainer
  • 进入容器调试:docker run -it --entrypoint /bin/bash myimage

2. 端口无法访问

  • 检查容器是否正确映射端口:docker port mycontainer
  • 检查应用是否监听0.0.0.0而不是127.0.0.1
  • 检查防火墙是否开放端口
  • 检查宿主机端口是否被占用

3. 容器之间无法通信

  • 确保容器在同一个网络中
  • 使用容器名而不是IP访问(Docker内置DNS)
  • 检查容器的端口是否正确expose
  • 检查防火墙和网络配置

4. 数据丢失

  • 确保使用了数据卷持久化数据
  • 不要随便删除数据卷
  • 定期备份数据卷
  • 检查数据卷挂载路径是否正确

5. 镜像构建慢

  • 检查.dockerignore是否排除了不需要的文件
  • 合理安排Dockerfile指令顺序,利用缓存
  • 使用国内镜像源加速
  • 合并RUN指令,减少层数

总结

Docker是2016年最流行的容器化技术,让应用部署变得简单、高效、可移植。本文从Docker核心概念、Dockerfile编写最佳实践、镜像优化、Docker Compose多容器编排、数据卷与网络、生产环境部署、日志与监控、安全加固、CI/CD集成、常见问题排错等方面,系统讲解了Docker容器化部署实战。

Docker部署的核心要点:

  1. 编写好的Dockerfile:选择合适的基础镜像,减少层数,利用缓存,使用非root用户
  2. 优化镜像:减小体积,加快构建,保证安全
  3. 使用Docker Compose编排多容器应用
  4. 生产环境注意:版本固定、数据持久化、健康检查、资源限制、日志配置、安全加固
  5. 监控和日志:监控容器状态,集中管理日志
  6. CI/CD集成:自动化构建、测试、部署

Docker改变了软件开发和运维的方式,让"一次构建,到处运行"成为现实。掌握Docker容器化部署,是现代开发者和运维人员的必备技能。希望本文能帮助你更好地使用Docker,将应用快速、可靠地部署到生产环境。