Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口。

2013年Docker诞生以来,容器化技术迅速发展,改变了软件开发、测试、部署的方式。2016年,Docker已经非常成熟,成为DevOps和云计算领域的标配技术。作为程序员,掌握Docker是必备技能之一。今天就来分享Docker容器化部署的完整指南。

Docker简介

1. 什么是Docker

Docker是一个开源的应用容器引擎,基于Go语言开发,由dotCloud公司(后改名为Docker Inc.)于2013年推出。Docker可以让开发者将应用及其依赖打包到一个轻量级、可移植的容器中,然后发布到任何流行的Linux机器上运行。

Docker的核心概念:

  • 镜像(Image):Docker镜像是一个只读的模板,包含运行应用所需的代码、运行时、库、环境变量、配置文件等。镜像可以用来创建Docker容器,类似于面向对象编程中的类。
  • 容器(Container):Docker容器是镜像运行时的实例,是一个独立运行的应用或一组应用,以及它们的运行环境。容器可以被启动、停止、删除,类似于面向对象编程中的对象。
  • 仓库(Repository):Docker仓库是存放镜像的地方,分为公共仓库和私有仓库。Docker Hub是最大的公共仓库,存放了大量官方和社区创建的镜像。

Docker的特点:

  • 轻量级:容器共享主机内核,启动快,资源占用少,比虚拟机更轻量
  • 可移植:一次构建,到处运行,容器可以在任何支持Docker的环境中运行
  • 隔离性:容器之间相互隔离,应用和依赖运行在独立的环境中,互不干扰
  • 标准化:Docker提供了标准化的打包、分发、部署方式,简化了应用的交付流程
  • 可复制:容器可以快速复制和扩展,适合弹性伸缩和集群部署
  • 版本控制:镜像支持版本控制,可以追溯历史版本,方便回滚

2. Docker与虚拟机的区别

Docker容器和虚拟机(VM)都是虚拟化技术,但是它们有本质的区别:

特性Docker容器虚拟机
虚拟化层次操作系统级虚拟化硬件级虚拟化
内核共享主机内核每个VM有独立内核
启动速度秒级启动分钟级启动
资源占用轻量,MB级重量,GB级
性能接近原生性能有一定性能损耗
隔离性进程级隔离完全隔离
可移植性高,一次构建到处运行较低,依赖虚拟化平台

简单来说,虚拟机是在硬件层面虚拟化,每个虚拟机都有完整的操作系统,资源占用大,启动慢;而Docker容器是在操作系统层面虚拟化,容器共享主机内核,只包含应用和依赖,资源占用小,启动快。

3. Docker的应用场景

  • 应用部署:将应用和依赖打包成镜像,快速部署到生产环境,避免环境不一致的问题
  • 持续集成/持续部署(CI/CD):Docker与CI/CD工具结合,实现自动化构建、测试、部署
  • 微服务架构:每个微服务打包成一个容器,独立部署、独立扩展、独立维护
  • 开发环境:开发者可以用Docker快速搭建一致的开发环境,避免"在我机器上能跑"的问题
  • 测试环境:测试人员可以用Docker快速创建和销毁测试环境,提高测试效率
  • 弹性伸缩:容器可以快速启动和停止,配合编排工具实现自动弹性伸缩
  • 多租户隔离:每个租户的应用运行在独立的容器中,实现资源隔离和安全隔离
  • 遗留应用迁移:将老旧应用打包成容器,迁移到新的服务器或云平台

Docker安装

1. CentOS安装Docker

# 卸载旧版本
sudo yum remove docker docker-common docker-selinux docker-engine

# 安装依赖
sudo yum install -y yum-utils device-mapper-persistent-data lvm2

# 添加Docker源(使用国内阿里云源,速度更快)
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

# 安装Docker CE
sudo yum install -y docker-ce

# 启动Docker
sudo systemctl start docker

# 设置开机自启
sudo systemctl enable docker

# 验证安装
docker --version
docker info

2. Ubuntu安装Docker

# 卸载旧版本
sudo apt-get remove docker docker-engine docker.io containerd runc

# 更新apt包索引
sudo apt-get update

# 安装依赖
sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common

# 添加Docker官方GPG密钥(使用国内源)
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -

# 添加Docker源
sudo add-apt-repository "deb [arch=amd64] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable"

# 更新apt包索引
sudo apt-get update

# 安装Docker CE
sudo apt-get install -y docker-ce

# 启动Docker
sudo systemctl start docker

# 设置开机自启
sudo systemctl enable docker

# 验证安装
docker --version

3. 配置Docker镜像加速

由于国内网络原因,从Docker Hub拉取镜像速度较慢,建议配置国内镜像加速器,如阿里云、网易云、中科大等。

# 创建Docker配置目录
sudo mkdir -p /etc/docker

# 配置镜像加速器(以阿里云为例,需要替换为自己的加速器地址)
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": [
    "https://your-mirror.mirror.aliyuncs.com",
    "https://docker.mirrors.ustc.edu.cn",
    "https://hub-mirror.c.163.com"
  ]
}
EOF

# 重启Docker
sudo systemctl daemon-reload
sudo systemctl restart docker

# 验证配置
docker info

Docker常用命令

1. 镜像操作

# 搜索镜像
docker search nginx

# 拉取镜像
docker pull nginx
docker pull nginx:1.21
docker pull ubuntu:20.04

# 列出本地镜像
docker images
docker image ls

# 查看镜像详情
docker inspect nginx

# 删除镜像
docker rmi nginx
docker image rm nginx
docker rmi -f nginx  # 强制删除

# 导出镜像
docker save -o nginx.tar nginx

# 导入镜像
docker load -i nginx.tar

# 给镜像打标签
docker tag nginx:latest myregistry.com/nginx:1.0

# 推送镜像到仓库
docker push myregistry.com/nginx:1.0

2. 容器操作

# 创建并启动容器
docker run nginx
docker run -d nginx  # 后台运行
docker run -d --name mynginx nginx  # 指定容器名
docker run -d -p 8080:80 nginx  # 端口映射(主机8080映射到容器80)
docker run -d -v /host/path:/container/path nginx  # 挂载数据卷
docker run -e ENV_VAR=value nginx  # 设置环境变量
docker run -it ubuntu /bin/bash  # 交互式运行

# 列出容器
docker ps  # 运行中的容器
docker ps -a  # 所有容器(包括已停止的)
docker container ls -a

# 启动/停止/重启容器
docker start mynginx
docker stop mynginx
docker restart mynginx

# 进入容器
docker exec -it mynginx /bin/bash
docker attach mynginx  # 附加到容器(不推荐,退出会停止容器)

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

# 查看容器详情
docker inspect mynginx

# 查看容器内进程
docker top mynginx

# 复制文件到容器/从容器复制
docker cp /host/path mynginx:/container/path
docker cp mynginx:/container/path /host/path

# 删除容器
docker rm mynginx
docker rm -f mynginx  # 强制删除(运行中的也能删)
docker container prune  # 删除所有已停止的容器

# 暂停/恢复容器
docker pause mynginx
docker unpause mynginx

3. 数据卷操作

# 创建数据卷
docker volume create myvolume

# 列出数据卷
docker volume ls

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

# 使用数据卷启动容器
docker run -d -v myvolume:/data nginx

# 挂载主机目录作为数据卷
docker run -d -v /host/data:/data nginx

# 删除数据卷
docker volume rm myvolume
docker volume prune  # 删除所有未使用的数据卷

4. 网络操作

# 列出网络
docker network ls

# 创建网络
docker network create mynetwork
docker network create --driver bridge mynetwork

# 查看网络详情
docker inspect mynetwork

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

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

# 删除网络
docker network rm mynetwork
docker network prune  # 删除所有未使用的网络

Dockerfile编写

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

1. Dockerfile常用指令

# FROM:指定基础镜像,必须是第一条指令
FROM ubuntu:20.04

# MAINTAINER:维护者信息(已废弃,推荐用LABEL)
MAINTAINER Your Name <your@email.com>

# LABEL:添加元数据
LABEL maintainer="Your Name <your@email.com>"
LABEL version="1.0"
LABEL description="This is a custom image"

# RUN:执行命令(构建时执行)
RUN apt-get update && apt-get install -y nginx
RUN echo "Hello Docker" > /var/www/html/index.html

# CMD:容器启动时执行的命令(可以被docker run参数覆盖)
CMD ["nginx", "-g", "daemon off;"]

# ENTRYPOINT:容器启动时执行的命令(不会被docker run参数覆盖,参数会追加)
ENTRYPOINT ["nginx", "-g", "daemon off;"]

# WORKDIR:设置工作目录
WORKDIR /app

# COPY:复制文件到镜像中(从构建上下文复制)
COPY . /app
COPY package.json /app/package.json

# ADD:复制文件到镜像中(支持URL和自动解压tar)
ADD app.tar.gz /app
ADD https://example.com/file.txt /app

# ENV:设置环境变量
ENV NODE_ENV=production
ENV PORT=8080

# EXPOSE:声明容器监听的端口
EXPOSE 80
EXPOSE 443

# VOLUME:声明数据卷
VOLUME ["/data"]

# USER:指定运行用户
USER nginx

# ARG:构建参数(构建时传入,用--build-arg)
ARG VERSION=1.0
ENV APP_VERSION=$VERSION

# HEALTHCHECK:健康检查
HEALTHCHECK --interval=30s --timeout=3s CMD curl -f http://localhost/ || exit 1

2. Dockerfile示例:Nginx镜像

# 基础镜像
FROM nginx:1.21-alpine

# 维护者
LABEL maintainer="Your Name <your@email.com>"

# 设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

# 复制网站文件
COPY ./html /usr/share/nginx/html

# 复制Nginx配置
COPY ./nginx.conf /etc/nginx/nginx.conf

# 暴露端口
EXPOSE 80 443

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s CMD wget -q --spider http://localhost/ || exit 1

# 启动Nginx
CMD ["nginx", "-g", "daemon off;"]

3. Dockerfile示例:Node.js应用

# 基础镜像
FROM node:16-alpine

# 维护者
LABEL maintainer="Your Name <your@email.com>"

# 设置工作目录
WORKDIR /app

# 设置环境变量
ENV NODE_ENV=production
ENV PORT=3000

# 先复制package.json,利用Docker缓存
COPY package*.json ./

# 安装依赖
RUN npm install --production

# 复制应用代码
COPY . .

# 暴露端口
EXPOSE 3000

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s CMD curl -f http://localhost:3000/health || exit 1

# 启动应用
CMD ["node", "app.js"]

4. Dockerfile最佳实践

  • 使用轻量级基础镜像:优先使用alpine版本镜像,体积小,安全
  • 利用构建缓存:将变化少的指令放在前面(如安装依赖),变化多的放在后面(如复制代码),充分利用Docker缓存
  • 合并RUN指令:多个RUN指令合并成一个,减少镜像层数,减小镜像体积
  • 清理缓存:安装完依赖后及时清理包管理器缓存,减小镜像体积
  • 使用.dockerignore:排除不需要的文件,减小构建上下文,加快构建速度
  • 不要在镜像中存储敏感信息:密码、密钥等不要写在Dockerfile中,使用环境变量或密钥管理
  • 指定具体版本标签:不要用latest标签,指定具体版本,保证可重复性
  • 多阶段构建:使用多阶段构建,编译环境和运行环境分离,减小最终镜像体积
  • 非root用户运行:创建普通用户,用USER指令切换,提高安全性

5. .dockerignore示例

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

Docker Compose

Docker Compose是Docker官方的编排工具,用于定义和运行多容器Docker应用。通过一个YAML文件来配置应用的服务,然后用一条命令就可以创建并启动所有服务。

1. 安装Docker Compose

# 下载Docker Compose(使用国内镜像加速)
sudo curl -L "https://get.daocloud.io/docker/compose/releases/download/v2.20.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

# 添加执行权限
sudo chmod +x /usr/local/bin/docker-compose

# 验证安装
docker-compose --version

2. docker-compose.yml示例:LNMP环境

version: '3.8'

services:
  # Nginx服务
  nginx:
    image: nginx:1.21-alpine
    container_name: lnmp-nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./www:/var/www/html
      - ./logs/nginx:/var/log/nginx
    depends_on:
      - php
      - mysql
    restart: always
    networks:
      - lnmp-network

  # PHP服务
  php:
    image: php:7.4-fpm-alpine
    container_name: lnmp-php
    volumes:
      - ./www:/var/www/html
      - ./php/php.ini:/usr/local/etc/php/php.ini
    depends_on:
      - mysql
      - redis
    restart: always
    networks:
      - lnmp-network

  # MySQL服务
  mysql:
    image: mysql:5.7
    container_name: lnmp-mysql
    ports:
      - "3306:3306"
    volumes:
      - ./mysql/data:/var/lib/mysql
      - ./mysql/my.cnf:/etc/mysql/my.cnf
    environment:
      MYSQL_ROOT_PASSWORD: root123456
      MYSQL_DATABASE: blog
      MYSQL_USER: blog
      MYSQL_PASSWORD: blog123456
    restart: always
    networks:
      - lnmp-network

  # Redis服务
  redis:
    image: redis:6-alpine
    container_name: lnmp-redis
    ports:
      - "6379:6379"
    volumes:
      - ./redis/data:/data
      - ./redis/redis.conf:/etc/redis/redis.conf
    command: redis-server /etc/redis/redis.conf
    restart: always
    networks:
      - lnmp-network

# 网络
networks:
  lnmp-network:
    driver: bridge

3. Docker Compose常用命令

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

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

# 停止所有服务
docker-compose down

# 停止并删除容器、网络、卷
docker-compose down -v

# 查看服务状态
docker-compose ps

# 查看服务日志
docker-compose logs
docker-compose logs -f nginx
docker-compose logs --tail 100

# 进入容器
docker-compose exec nginx /bin/sh
docker-compose exec php /bin/sh

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

# 拉取镜像
docker-compose pull

# 重启服务
docker-compose restart
docker-compose restart nginx

# 暂停/恢复服务
docker-compose pause
docker-compose unpause

# 查看配置
docker-compose config

Docker部署实战:部署PHP博客

下面以部署一个PHP+MySQL+Redis的博客为例,演示如何用Docker Compose快速部署。

1. 项目结构

blog/
├── docker-compose.yml      # Docker Compose配置
├── nginx/
│   ├── nginx.conf          # Nginx主配置
│   └── conf.d/
│       └── blog.conf       # 博客站点配置
├── php/
│   └── php.ini             # PHP配置
├── mysql/
│   ├── my.cnf              # MySQL配置
│   └── data/               # MySQL数据目录
├── redis/
│   ├── redis.conf          # Redis配置
│   └── data/               # Redis数据目录
├── logs/
│   ├── nginx/              # Nginx日志
│   └── php/                # PHP日志
└── www/                    # 网站根目录
    └── index.php

2. nginx/conf.d/blog.conf

server {
    listen 80;
    server_name localhost;
    root /var/www/html;
    index index.php index.html;

    # 伪静态
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    # PHP处理
    location ~ \.php$ {
        fastcgi_pass php:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    # 静态文件缓存
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|webp)$ {
        expires 30d;
        access_log off;
    }

    # 禁止访问隐藏文件
    location ~ /\. {
        deny all;
    }
}

3. www/index.php

<?php
// 连接MySQL
$pdo = new PDO('mysql:host=mysql;dbname=blog;charset=utf8mb4', 'blog', 'blog123456');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// 连接Redis
$redis = new Redis();
$redis->connect('redis', 6379);

// 测试
echo "<h1>Hello Docker!</h1>";
echo "<p>PHP版本: " . phpversion() . "</p>";

// MySQL测试
$stmt = $pdo->query("SELECT VERSION() as version");
$mysqlVersion = $stmt->fetch(PDO::FETCH_ASSOC)['version'];
echo "<p>MySQL版本: {$mysqlVersion}</p>";

// Redis测试
$redis->set('test', 'Hello Redis!');
$redisTest = $redis->get('test');
echo "<p>Redis测试: {$redisTest}</p>";

echo "<p>博客部署成功!</p>";

4. 启动服务

# 进入项目目录
cd /path/to/blog

# 启动所有服务
docker-compose up -d

# 查看服务状态
docker-compose ps

# 查看日志
docker-compose logs -f

# 访问网站
# 浏览器打开 http://localhost

5. 常用运维命令

# 重启Nginx
docker-compose restart nginx

# 重新加载Nginx配置
docker-compose exec nginx nginx -s reload

# 进入MySQL
docker-compose exec mysql mysql -u root -p

# 备份数据库
docker-compose exec mysql mysqldump -u root -p blog > backup.sql

# 导入数据库
docker-compose exec -T mysql mysql -u root -p blog < backup.sql

# 查看Redis
docker-compose exec redis redis-cli

# 查看PHP日志
docker-compose logs php

# 更新代码后重启PHP
docker-compose restart php

Docker安全加固

  1. 不要用root用户运行容器:在Dockerfile中创建普通用户,用USER指令切换,容器内应用以普通用户运行
  2. 不要在镜像中存储敏感信息:密码、密钥、证书等不要写在Dockerfile或代码中,使用环境变量、Docker secrets或密钥管理工具
  3. 限制容器资源:启动容器时用--memory、--cpus等参数限制内存和CPU使用,防止容器占用过多资源
  4. 只读文件系统:用--read-only参数将容器根文件系统设为只读,需要写入的目录用数据卷挂载
  5. 禁用不需要的能力:用--cap-drop=ALL禁用所有Linux capabilities,只添加需要的能力
  6. 不要使用--privileged:除非必要,不要使用特权模式,特权模式容器拥有主机所有权限,非常危险
  7. 及时更新基础镜像:定期更新基础镜像,修复已知安全漏洞
  8. 扫描镜像漏洞:使用Trivy、Clair等工具扫描镜像中的安全漏洞
  9. 限制容器网络:使用自定义网络,不要用--network=host,容器之间只开放必要端口
  10. 使用可信镜像:只从可信的仓库拉取镜像,优先使用官方镜像,不要随意使用不明来源的镜像

学习建议

  1. 动手实践:Docker是实践性很强的技术,光看教程学不会,一定要多动手。在自己的电脑或服务器上安装Docker,跑几个容器,写几个Dockerfile,用Docker Compose部署几个应用
  2. 理解核心概念:镜像、容器、仓库、数据卷、网络,这些核心概念一定要理解清楚,这是学好Docker的基础
  3. 掌握常用命令:Docker命令很多,不需要全部记住,但是常用的镜像操作、容器操作、Docker Compose命令一定要熟练
  4. 学会写Dockerfile:Dockerfile是构建镜像的基础,要掌握常用指令,学会写高效、安全的Dockerfile
  5. 学习Docker Compose:实际项目中通常是多容器应用,Docker Compose是必备技能,要学会用docker-compose.yml编排多容器应用
  6. 了解容器编排:如果想深入学习,可以了解Kubernetes(K8s),它是目前最流行的容器编排工具,用于大规模容器集群管理
  7. 结合实际项目:把自己的项目用Docker部署,在实践中学习和提高。可以从简单的静态网站开始,逐步到PHP/Node.js应用,再到微服务架构

总结

Docker是现代软件开发和部署的革命性技术,它改变了应用的打包、分发、部署方式,让"一次构建,到处运行"成为现实。掌握Docker,不仅能提高开发和部署效率,还能让我们更好地理解微服务、DevOps、云计算等现代技术理念。

Docker的学习曲线并不陡峭,核心概念不多,常用命令也不难掌握。关键是要多动手实践,在实际项目中应用和提高。从跑一个简单的容器开始,到写Dockerfile,再到用Docker Compose编排多容器应用,逐步深入,相信你很快就能掌握Docker。

容器化是未来的趋势,Docker作为容器技术的代表,已经成为程序员的必备技能。希望这篇指南能帮助你入门Docker,开启容器化之旅!