Docker,是目前世界上,最流行、最强大的容器化技术。

对于程序员来说,Docker,已经成为了必备技能。无论是开发环境搭建,还是应用部署,还是微服务架构,Docker,都能帮助我们,更高效地完成工作。可以说,不会用Docker的程序员,在2015年,已经开始落伍了。

我自己,从2014年开始接触Docker,到现在,已经用了一年多了。从最初的,觉得Docker很神奇,到后来,能够熟练使用Docker,搭建开发环境,部署应用,构建镜像,编排容器,一路走来,踩了不少坑,也总结了不少经验。我见过太多的开发者,因为环境问题,而浪费大量时间;因为部署问题,而导致应用无法运行;因为依赖冲突,而导致各种奇怪的bug。其实,只要用了Docker,这些问题,大部分都能解决。

2015年,Docker,已经成为了容器化技术的绝对主流。Docker公司,在2015年,获得了巨额融资,估值超过10亿美元;Docker 1.7、1.8、1.9版本,陆续发布,功能越来越强大;Docker Compose、Docker Swarm、Docker Machine等工具,也越来越成熟;各大云服务商,包括AWS、Google Cloud、Microsoft Azure、阿里云、腾讯云,都开始支持Docker。Docker,已经从一个,小众的技术,变成了,主流的技术。

今天,分享Docker入门,从什么是Docker,到Docker的基本概念,到Docker的安装和使用,到Dockerfile,到Docker Compose,到最佳实践,全方位讲解Docker的使用方法和技巧,帮你掌握这个强大的容器化技术。

什么是Docker

在讲Docker之前,先简单介绍一下,什么是容器,什么是虚拟机,它们有什么区别。

1. 虚拟机

虚拟机(Virtual Machine),是通过虚拟化技术,在一台物理机上,模拟出多台虚拟的计算机。每台虚拟机,都有自己的操作系统(Guest OS),有自己的CPU、内存、硬盘、网卡等虚拟硬件。

虚拟机的优点是,隔离性好,每台虚拟机,都是完全独立的,互不影响。缺点是,资源占用大,每台虚拟机,都需要运行一个完整的操作系统,启动慢,占用大量的CPU、内存、硬盘资源。

常见的虚拟机软件有,VMware、VirtualBox、KVM、Xen等。

2. 容器

容器(Container),是一种轻量级的虚拟化技术。它,不像虚拟机那样,需要运行一个完整的操作系统,而是,共享宿主机的操作系统内核,只隔离进程和资源。

容器的优点是,轻量级,启动快(秒级启动),资源占用小(一个容器,只占用几十MB的内存),可以在一台物理机上,运行成百上千个容器。缺点是,隔离性不如虚拟机,所有容器,共享宿主机的内核,如果内核有漏洞,可能会影响所有容器。

Docker,就是目前最流行的容器技术。它,让容器的使用,变得非常简单和方便。

3. Docker是什么

Docker,是一个开源的应用容器引擎,基于Go语言开发。它,可以让开发者,把应用和依赖,打包到一个轻量级、可移植的容器中,然后,发布到任何流行的Linux机器上,也可以实现虚拟化。

简单来说,Docker,就是一个,用来装应用的"集装箱"。你把你的应用,和它需要的所有依赖(库、配置文件、环境变量等),都打包到一个Docker镜像里。然后,这个镜像,可以在任何安装了Docker的机器上,运行起来,而且,运行环境,完全一致,不会出现"在我机器上能跑,在你机器上跑不了"的问题。

Docker的口号是:"Build, Ship, and Run Any App, Anywhere."(构建、交付、运行任何应用,在任何地方。)

4. Docker的优势

Docker,有以下优势:

  1. 快速交付和部署:开发者,可以用Docker,快速创建一个容器,运行应用;测试和运维,可以直接用同一个镜像,部署到测试环境和生产环境。整个流程,快速、一致、可重复。
  1. 环境一致性:Docker镜像,包含了应用运行所需的所有依赖,确保了,应用在开发、测试、生产环境,运行环境完全一致,不会出现"在我机器上能跑"的问题。
  1. 轻量级和高性能:Docker容器,共享宿主机内核,不需要运行完整的操作系统,启动快(秒级),资源占用小(几十MB内存),性能接近原生。
  1. 隔离性:Docker容器,之间是相互隔离的,每个容器,有自己的文件系统、进程空间、网络空间。一个容器的崩溃,不会影响其他容器。
  1. 可移植性:Docker镜像,可以在任何安装了Docker的机器上运行,无论是笔记本电脑,还是服务器,还是云服务器,都可以。一次构建,到处运行。
  1. 版本控制和回滚:Docker镜像,有版本控制,可以像Git一样,管理镜像的版本。如果新版本有问题,可以快速回滚到旧版本。
  1. 微服务架构:Docker,非常适合微服务架构。每个微服务,可以打包成一个镜像,独立部署,独立扩展,互不影响。
  1. 生态丰富:Docker,有丰富的生态系统。Docker Hub,上有大量的官方镜像和社区镜像,可以直接使用。还有Docker Compose、Docker Swarm、Kubernetes等工具,帮助管理和编排容器。

Docker的基本概念

要学好Docker,首先要理解Docker的几个基本概念:镜像、容器、仓库、Dockerfile、数据卷、网络。

1. 镜像(Image)

Docker镜像,是一个只读的模板,包含了运行应用所需的所有内容:代码、运行时、库、环境变量、配置文件等。

镜像,是用来创建容器的。一个镜像,可以创建多个容器。镜像,是只读的,不能修改;容器,是可写的,可以修改。

镜像,是分层存储的。一个镜像,由多个层(Layer)组成,每一层,对应Dockerfile中的一条指令。这种分层设计,让镜像的复用、共享、构建,都非常高效。比如,多个镜像,可以共享同一个基础层;修改镜像时,只需要修改对应的层,不需要重新构建整个镜像。

镜像,可以通过Dockerfile构建,也可以从Docker Hub等仓库,拉取现成的镜像。

常见的镜像命名格式:仓库名/镜像名:标签。比如,nginx:latestubuntu:14.04mysql:5.6。如果不指定标签,默认是latest

2. 容器(Container)

Docker容器,是镜像运行时的实例。它,是一个独立的、可执行的应用环境,包含了镜像的内容,以及,运行时的可写层。

容器,可以被启动、停止、删除、重启。每个容器,都是相互隔离的,有自己的文件系统、进程空间、网络空间。

容器的生命周期:

  • 创建(create):用镜像,创建一个容器,但不启动
  • 启动(start):启动一个已创建的容器
  • 运行(run):创建并启动一个容器(相当于create + start)
  • 停止(stop):停止一个运行中的容器
  • 重启(restart):重启一个容器
  • 删除(rm):删除一个容器(容器必须是停止状态,或者用-f强制删除)
  • 暂停(pause):暂停容器中的所有进程
  • 恢复(unpause):恢复暂停的容器

容器,是临时的。容器删除后,容器中的数据(可写层),也会被删除。如果,需要持久化数据,需要使用数据卷(Volume),或者,把数据目录,挂载到宿主机。

3. 仓库(Repository)

Docker仓库,是用来存储和分发镜像的地方。它,类似于代码的GitHub,是镜像的"代码托管平台"。

Docker仓库,分为公共仓库和私有仓库:

  • 公共仓库:如Docker Hub(hub.docker.com),是最大的公共镜像仓库,有大量的官方镜像和社区镜像,可以免费拉取和使用。国内,也有一些公共镜像仓库,如阿里云容器镜像服务、网易云镜像服务、DaoCloud镜像市场等,可以加速镜像拉取。
  • 私有仓库:企业或个人,可以搭建自己的私有仓库,用来存储不公开的镜像。Docker官方提供了registry镜像,可以快速搭建私有仓库。也可以使用Harbor、Nexus等更强大的私有仓库解决方案。

仓库的操作:

  • docker pull:从仓库,拉取镜像
  • docker push:把本地镜像,推送到仓库
  • docker search:在仓库,搜索镜像

4. Dockerfile

Dockerfile,是一个文本文件,包含了一系列的指令,用来构建Docker镜像。

每条指令,对应镜像的一层。Docker,会按照Dockerfile中的指令顺序,逐层构建镜像。

常见的Dockerfile指令:

  • FROM:指定基础镜像。每条Dockerfile,第一条指令,必须是FROM。
  • MAINTAINER:指定维护者信息(已过时,推荐用LABEL)。
  • LABEL:给镜像添加元数据(如维护者、版本、描述等)。
  • RUN:在镜像构建过程中,执行命令。每条RUN指令,会在当前镜像的基础上,执行命令,然后,提交为新的一层。
  • CMD:指定容器启动时,默认执行的命令。如果,docker run时,指定了命令,会覆盖CMD。Dockerfile中,只能有一条CMD指令(如果有多条,只有最后一条生效)。
  • ENTRYPOINT:指定容器启动时,执行的命令。和CMD类似,但ENTRYPOINT,不会被docker run的命令参数覆盖,而是,会把命令参数,作为ENTRYPOINT的参数。
  • EXPOSE:声明容器运行时,监听的端口。这只是一个声明,不会自动映射端口。需要在docker run时,用-p参数,映射端口。
  • ENV:设置环境变量。
  • ADD:复制文件到镜像中。和COPY类似,但ADD,支持自动解压tar文件,以及,从URL下载文件。
  • COPY:复制文件到镜像中。推荐使用COPY,而不是ADD,除非需要自动解压或下载。
  • VOLUME:声明数据卷,用于持久化数据。
  • WORKDIR:设置工作目录。后续的RUN、CMD、ENTRYPOINT、COPY、ADD等指令,都会在这个目录下执行。
  • USER:设置运行容器的用户。
  • ARG:设置构建参数。可以在docker build时,用--build-arg传递参数。
  • ONBUILD:设置触发器。当这个镜像,被用作其他镜像的基础镜像时,会执行ONBUILD中的指令。

5. 数据卷(Volume)

数据卷,是Docker中,用来持久化数据的机制。它,是一个或多个容器,专门用来存放数据的目录,绕过了容器的可写层,直接存在于宿主机上。

数据卷的特点:

  • 数据卷,在容器创建时初始化
  • 数据卷,可以在容器之间,共享和重用
  • 对数据卷的修改,直接生效,不会影响镜像
  • 数据卷,不会随着容器的删除而删除
  • 数据卷,可以挂载到宿主机的指定目录

数据卷的使用方式:

  • docker run -v /data:创建一个数据卷,挂载到容器的/data目录,宿主机目录由Docker自动分配
  • docker run -v /host/path:/container/path:把宿主机的指定目录,挂载到容器的指定目录
  • docker run -v name:/container/path:创建一个命名数据卷,挂载到容器的指定目录
  • Dockerfile中的VOLUME指令:声明数据卷

6. 网络(Network)

Docker,提供了网络功能,让容器之间,可以相互通信,也可以和宿主机、外部网络通信。

Docker的网络模式:

  • bridge(默认):Docker会创建一个docker0的虚拟网桥,所有容器,默认连接到这个网桥。容器之间,可以通过IP相互通信;容器,可以通过网桥,访问外部网络;外部网络,需要通过端口映射,才能访问容器。
  • host:容器,共享宿主机的网络命名空间。容器,直接使用宿主机的IP和端口,性能最好,但隔离性差,端口容易冲突。
  • none:容器,没有网络,只能通过本地回环(lo)通信,不能访问外部网络,外部网络也不能访问它。用于安全要求高的场景。
  • container:容器,共享另一个容器的网络命名空间。两个容器,可以通过localhost相互通信。

Docker的网络操作:

  • docker network ls:列出网络
  • docker network create:创建网络
  • docker network inspect:查看网络详情
  • docker network connect:把容器连接到网络
  • docker network disconnect:把容器从网络断开
  • docker network rm:删除网络

Docker的安装

Docker,支持在Linux、Windows、macOS上安装。但Docker,原生是基于Linux的,在Windows和macOS上,需要通过虚拟机运行。

1. Linux安装

在Linux上安装Docker,非常简单。以Ubuntu为例:

# 更新软件源
sudo apt-get update

# 安装依赖
sudo apt-get install -y apt-transport-https ca-certificates

# 添加Docker官方GPG密钥
sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D

# 添加Docker软件源
echo "deb https://apt.dockerproject.org/repo ubuntu-trusty main" | sudo tee /etc/apt/sources.list.d/docker.list

# 更新软件源
sudo apt-get update

# 安装Docker
sudo apt-get install -y docker-engine

# 启动Docker服务
sudo service docker start

# 设置开机自启
sudo update-rc.d docker defaults

# 验证安装
docker --version
docker info

也可以用更简单的方式,使用官方的安装脚本:

curl -fsSL https://get.docker.com/ | sh

2. 配置国内镜像加速

由于Docker Hub在国外,拉取镜像的速度,可能比较慢。可以配置国内的镜像加速器,提升拉取速度。

以阿里云为例:

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

# 配置镜像加速器
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://你的加速器地址.mirror.aliyuncs.com"]
}
EOF

# 重启Docker服务
sudo service docker restart

阿里云的加速器地址,可以在阿里云容器镜像服务的控制台,免费获取。

也可以使用其他国内镜像源,如网易云、DaoCloud等。

3. Windows和macOS安装

在Windows和macOS上,可以安装Docker Toolbox,它包含了Docker Engine、Docker Compose、Docker Machine、Kitematic等工具,以及一个VirtualBox虚拟机。

下载地址:https://www.docker.com/products/docker-toolbox

安装完成后,运行Docker Quickstart Terminal,会自动创建一个默认的虚拟机,并启动Docker。

注意:在Windows和macOS上,Docker是运行在虚拟机里的,所以,容器的IP,是虚拟机的IP(通常是192.168.99.100),而不是localhost。访问容器时,需要用虚拟机的IP。

2016年以后,Docker推出了Docker for Windows和Docker for Mac,原生支持,不需要VirtualBox,性能更好。但在2015年,还是Docker Toolbox为主。

Docker常用命令

1. 镜像相关命令

# 搜索镜像
docker search nginx

# 拉取镜像
docker pull nginx
docker pull nginx:1.9
docker pull ubuntu:14.04

# 列出本地镜像
docker images
docker images -a  # 包括中间层镜像
docker images -q  # 只显示镜像ID

# 查看镜像详情
docker inspect nginx

# 查看镜像历史
docker history nginx

# 给镜像打标签
docker tag nginx:latest mynginx:1.0

# 删除镜像
docker rmi nginx
docker rmi -f nginx  # 强制删除
docker rmi $(docker images -q)  # 删除所有镜像

# 构建镜像(在Dockerfile所在目录)
docker build -t myapp:1.0 .
docker build -t myapp:1.0 -f /path/to/Dockerfile .
docker build --no-cache -t myapp:1.0 .  # 不使用缓存

# 导出镜像为tar文件
docker save -o myapp.tar myapp:1.0

# 从tar文件导入镜像
docker load -i myapp.tar

# 推送镜像到仓库
docker push myrepo/myapp:1.0

2. 容器相关命令

# 创建并启动容器
docker run nginx
docker run -d nginx  # 后台运行
docker run -d -p 8080:80 nginx  # 端口映射,宿主机8080映射到容器80
docker run -d -p 8080:80 --name mynginx nginx  # 指定容器名
docker run -d -v /data:/var/www/html nginx  # 挂载数据卷
docker run -d -e MYSQL_ROOT_PASSWORD=123456 mysql  # 设置环境变量
docker run -it ubuntu /bin/bash  # 交互式运行,进入容器
docker run --rm nginx  # 容器停止后自动删除

# 列出容器
docker ps  # 运行中的容器
docker ps -a  # 所有容器(包括停止的)
docker ps -q  # 只显示容器ID
docker ps -a -q  # 所有容器ID

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

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

# 进入容器
docker exec -it mynginx /bin/bash
docker exec -it mynginx nginx -v  # 在容器中执行命令

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

# 查看容器详情
docker inspect mynginx

# 查看容器进程
docker top mynginx

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

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

# 删除容器
docker rm mynginx
docker rm -f mynginx  # 强制删除(运行中的也可以删)
docker rm $(docker ps -a -q)  # 删除所有容器
docker rm $(docker ps -a -q -f status=exited)  # 删除所有已停止的容器

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

# 重命名容器
docker rename oldname newname

3. 仓库相关命令

# 登录仓库
docker login
docker login registry.example.com

# 登出仓库
docker logout

# 搜索镜像
docker search nginx

# 拉取镜像
docker pull nginx

# 推送镜像
docker push myrepo/myapp:1.0

4. 数据卷相关命令

# 创建数据卷
docker volume create myvolume

# 列出数据卷
docker volume ls

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

# 删除数据卷
docker volume rm myvolume

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

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

5. 网络相关命令

# 列出网络
docker network ls

# 创建网络
docker network create mynetwork
docker network create --driver bridge mynetwork
docker network create --subnet 172.20.0.0/16 mynetwork

# 查看网络详情
docker network inspect mynetwork

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

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

# 删除网络
docker network rm mynetwork

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

# 使用网络启动容器
docker run -d --network mynetwork --name mycontainer nginx

Dockerfile最佳实践

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

1. 选择合适的基础镜像

  • 尽量使用官方镜像,安全可靠
  • 尽量使用小的基础镜像,如alpine(只有5MB左右),而不是ubuntu(几百MB)
  • 指定基础镜像的版本标签,不要用latest,避免不可预测的更新
# 好的例子
FROM node:4.2-alpine

# 不好的例子
FROM ubuntu:latest

2. 合并RUN指令

每条RUN指令,都会创建一层。过多的层,会让镜像变大,构建变慢。应该把相关的命令,合并到一条RUN指令中。

# 不好的例子(4层)
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y git
RUN apt-get clean

# 好的例子(1层)
RUN apt-get update && apt-get install -y \
    curl \
    git \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

3. 利用构建缓存

Docker,会缓存已经构建过的层。如果Dockerfile的指令,和之前一样,并且,上下文没有变化,就会使用缓存,跳过构建。

为了利用缓存,应该把变化少的指令,放在前面;变化多的指令,放在后面。比如,先安装依赖,再复制代码。

# 好的例子:先复制依赖文件,安装依赖,再复制代码
COPY package.json /app/
RUN cd /app && npm install
COPY . /app/

# 不好的例子:先复制所有代码,再安装依赖(每次代码变化,都会导致npm install重新执行)
COPY . /app/
RUN cd /app && npm install

4. 使用.dockerignore

.dockerignore文件,类似于.gitignore,用来指定,在构建镜像时,哪些文件,不需要发送给Docker守护进程。

使用.dockerignore,可以减少构建上下文的大小,加快构建速度,也可以避免,把敏感文件(如密码、密钥)打包到镜像中。

# .dockerignore
node_modules
npm-debug.log
.git
.gitignore
.env
*.log
dist
build

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

不要把密码、密钥、证书等敏感信息,写在Dockerfile中,或者打包到镜像中。应该使用环境变量,或者,在运行时,通过数据卷挂载。

# 不好的例子
ENV DB_PASSWORD=123456

# 好的例子:在运行时,通过环境变量传入
# docker run -e DB_PASSWORD=123456 myapp

6. 使用多阶段构建(Multi-stage Build)

多阶段构建,可以在一个Dockerfile中,使用多个FROM指令。每个阶段,都是一个独立的构建环境。最后,只需要把最终产物,复制到最后的镜像中,不需要把构建工具和中间产物,打包到镜像中。

注意:多阶段构建,是Docker 17.05以后才支持的。2015年的Docker 1.9,还不支持。但这个概念,很重要,提前了解。

# 构建阶段
FROM golang:1.5 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp .

# 最终镜像
FROM alpine:3.4
WORKDIR /app
COPY --from=builder /app/myapp .
CMD ["./myapp"]

7. 设置非root用户

默认情况下,容器中的进程,是以root用户运行的。这有安全风险。应该在Dockerfile中,创建一个普通用户,并切换到这个用户运行。

RUN useradd -m appuser
USER appuser

8. 合理使用CMD和ENTRYPOINT

  • CMD:指定容器启动时,默认执行的命令,可以被docker run的参数覆盖
  • ENTRYPOINT:指定容器启动时,执行的命令,不会被docker run的参数覆盖,而是把参数作为ENTRYPOINT的参数

如果,容器是用来运行一个固定的应用,推荐使用ENTRYPOINT;如果,容器是用来运行命令的,推荐使用CMD。

也可以组合使用:ENTRYPOINT指定固定的命令,CMD指定默认的参数。

ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]

9. 健康检查

Docker 1.12以后,支持HEALTHCHECK指令,用来检查容器的健康状态。2015年的Docker 1.9,还不支持,但可以了解。

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

10. 完整的Dockerfile示例

以一个Node.js应用为例:

# 基础镜像
FROM node:4.2-alpine

# 维护者
LABEL maintainer="your@email.com"

# 工作目录
WORKDIR /app

# 复制依赖文件
COPY package.json /app/

# 安装依赖
RUN npm install --production \
    && npm cache clean

# 复制代码
COPY . /app/

# 暴露端口
EXPOSE 3000

# 设置环境变量
ENV NODE_ENV=production

# 创建非root用户
RUN adduser -D appuser
USER appuser

# 启动命令
CMD ["node", "server.js"]

Docker Compose

Docker Compose,是Docker官方的编排工具,用来定义和运行多容器的Docker应用。

通过一个docker-compose.yml文件,可以定义应用需要的所有服务(容器),然后,用一条命令,启动所有服务。

1. 安装Docker Compose

在Linux上:

# 下载Docker Compose
curl -L https://github.com/docker/compose/releases/download/1.5.2/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose

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

# 验证安装
docker-compose --version

在Windows和macOS上,Docker Toolbox已经包含了Docker Compose,不需要单独安装。

2. docker-compose.yml示例

以一个WordPress应用为例(Nginx + PHP + MySQL):

web:
  image: nginx:latest
  ports:
    - "80:80"
  volumes:
    - ./nginx.conf:/etc/nginx/conf.d/default.conf
    - ./www:/var/www/html
  links:
    - php

php:
  image: php:5.6-fpm
  volumes:
    - ./www:/var/www/html
  links:
    - mysql

mysql:
  image: mysql:5.6
  environment:
    MYSQL_ROOT_PASSWORD: 123456
    MYSQL_DATABASE: wordpress
  volumes:
    - ./mysql:/var/lib/mysql

这个文件,定义了三个服务:web(Nginx)、php(PHP-FPM)、mysql(MySQL)。它们之间,通过links连接,共享数据卷。

3. Docker Compose常用命令

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

# 启动所有服务(前台运行,查看日志)
docker-compose up

# 停止所有服务
docker-compose stop

# 停止并删除所有容器、网络
docker-compose down

# 查看服务状态
docker-compose ps

# 查看服务日志
docker-compose logs
docker-compose logs -f web  # 查看web服务的日志

# 构建服务
docker-compose build
docker-compose build web  # 只构建web服务

# 拉取服务镜像
docker-compose pull

# 在服务中执行命令
docker-compose exec web /bin/bash
docker-compose exec mysql mysql -u root -p

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

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

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

4. Docker Compose最佳实践

  • 使用环境变量,管理配置(如数据库密码、端口等),不要硬编码
  • 使用数据卷,持久化数据
  • 合理使用links,连接服务
  • 为每个服务,指定镜像版本,不要用latest
  • 使用extends,复用配置
  • 使用多个docker-compose文件,区分不同环境(开发、测试、生产)

Docker常见问题和解决方法

1. 容器启动后立即退出

原因:容器的主进程,执行完后就退出了。Docker容器,需要有一个前台进程,持续运行。

解决方法:确保容器的CMD或ENTRYPOINT,是一个前台运行的命令。比如,Nginx要用nginx -g "daemon off;",MySQL要用mysqld,而不是后台运行的命令。

2. 无法访问容器端口

原因:没有做端口映射,或者,映射的端口不对。

解决方法:用docker run -p 宿主机端口:容器端口,做端口映射。用docker port 容器名,查看端口映射情况。用docker ps,查看PORTS列。

注意:在Windows和macOS上,Docker运行在虚拟机里,需要用虚拟机的IP(通常是192.168.99.100)访问,而不是localhost。

3. 容器之间无法通信

原因:容器不在同一个网络,或者,没有用links连接。

解决方法:用--link参数,连接容器;或者,创建自定义网络,把容器都连接到这个网络。在同一个网络中的容器,可以用容器名,相互访问。

4. 数据丢失

原因:数据存在容器的可写层,容器删除后,数据也丢失了。

解决方法:使用数据卷(-v参数),把数据目录,挂载到宿主机,或者,使用命名数据卷。这样,容器删除后,数据还在。

5. 镜像太大

原因:基础镜像太大,或者,Dockerfile写得不好,层数太多,没有清理缓存。

解决方法:

  • 使用小的基础镜像,如alpine
  • 合并RUN指令,减少层数
  • 在RUN指令中,清理缓存(如apt-get clean、yum clean、npm cache clean)
  • 使用多阶段构建,只把最终产物,打包到镜像中
  • 用.dockerignore,排除不需要的文件

6. 构建镜像很慢

原因:没有利用构建缓存,或者,构建上下文太大。

解决方法:

  • 把变化少的指令,放在前面,利用缓存
  • 用.dockerignore,减少构建上下文
  • 使用国内镜像源,加速依赖下载
  • 使用本地的依赖缓存,挂载到容器中

7. 权限问题

原因:容器中的进程,以root用户运行,或者,挂载的宿主机目录,权限不对。

解决方法:

  • 在Dockerfile中,创建非root用户,用USER指令切换
  • 挂载宿主机目录时,确保目录权限正确
  • --user参数,指定运行容器的用户

8. 时区问题

原因:Docker容器,默认时区是UTC,和国内的东八区,差8小时。

解决方法:

  • 在Dockerfile中,设置时区:RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
  • 或者,在运行容器时,挂载宿主机的时区文件:-v /etc/localtime:/etc/localtime:ro
  • 或者,设置环境变量:-e TZ=Asia/Shanghai

9. DNS问题

原因:容器无法解析域名。

解决方法:

  • 检查宿主机的DNS配置
  • 在运行容器时,指定DNS:--dns 8.8.8.8 --dns 114.114.114.114
  • 或者,在Docker守护进程配置中,设置默认DNS

10. 磁盘空间不足

原因:Docker的镜像、容器、数据卷,占用了大量磁盘空间。

解决方法:

  • 清理无用的镜像:docker rmi $(docker images -f "dangling=true" -q)
  • 清理已停止的容器:docker rm $(docker ps -a -q -f status=exited)
  • 清理无用的数据卷:docker volume rm $(docker volume ls -qf dangling=true)
  • 清理无用的网络:docker network prune
  • Docker 1.13以后,可以用docker system prune,一键清理所有无用资源

Docker最佳实践

1. 镜像管理

  • 一个容器,只运行一个进程(单一职责)
  • 镜像要小,使用alpine等小基础镜像
  • 指定镜像版本,不要用latest
  • 定期更新基础镜像,修复安全漏洞
  • 不要在镜像中,存储敏感信息
  • 使用.dockerignore,排除不需要的文件

2. 容器管理

  • 容器是临时的,不要在容器中存储数据
  • 使用数据卷,持久化数据
  • 容器启动要快,应用要支持快速启动和优雅停止
  • 为容器设置资源限制(CPU、内存),避免一个容器占用所有资源
  • 为容器设置健康检查
  • 容器出问题时,直接删除重建,不要在容器中修改(不可变基础设施)

3. 安全

  • 容器中的进程,以非root用户运行
  • 不要在容器中,运行sshd等不必要的服务
  • 只暴露需要的端口
  • 使用官方镜像,或可信的镜像
  • 定期扫描镜像漏洞
  • 限制容器的系统调用和权限(--cap-drop、--read-only等)

4. 日志

  • 应用日志,输出到stdout和stderr,不要写文件
  • docker logs查看日志
  • 用日志收集工具(如ELK、Fluentd),集中收集和管理日志
  • 不要在容器中,存储日志文件

5. 配置

  • 配置,通过环境变量传入,不要硬编码在镜像中
  • 不同环境(开发、测试、生产),使用不同的环境变量
  • 敏感配置(密码、密钥),使用密钥管理工具(如Vault),不要明文存储

6. 开发流程

  • 开发环境,用Docker,统一开发环境
  • CI/CD流程中,用Docker构建和测试
  • 镜像构建后,推送到私有仓库
  • 部署时,从私有仓库拉取镜像,部署到生产环境
  • 使用Docker Compose或Kubernetes,编排和管理容器

7. 监控

  • 监控容器的CPU、内存、磁盘、网络使用情况
  • 监控容器的健康状态
  • 监控应用的性能和错误
  • 使用监控工具(如Prometheus、Grafana、cAdvisor)

学习资源

推荐书籍

  • 《Docker技术入门与实战》:国内作者写的Docker书籍,非常详细,适合入门和深入学习。
  • 《第一本Docker书》(The Docker Book):James Turnbull写的,Docker入门经典,通俗易懂。
  • 《Docker实战》(Docker in Action):深入讲解Docker的使用和最佳实践,适合有一定基础的读者。

在线教程

  • Docker官方文档:https://docs.docker.com/ ,最权威、最全面的Docker文档。
  • Docker官方入门教程:https://docs.docker.com/engine/getstarted/
  • 阮一峰的Docker入门教程:http://www.ruanyifeng.com/blog/2016/04/dockercontainerguide.html
  • 菜鸟教程Docker教程:https://www.runoob.com/docker/docker-tutorial.html

视频教程

  • Docker官方YouTube频道:https://www.youtube.com/user/dockerrun
  • 极客学院Docker教程
  • 慕课网Docker教程

社区和工具

  • Docker Hub:https://hub.docker.com/ ,最大的Docker镜像仓库。
  • Docker GitHub:https://github.com/docker ,Docker的开源项目。
  • Docker论坛:https://forums.docker.com/
  • Stack Overflow Docker标签:https://stackoverflow.com/questions/tagged/docker
  • Kitematic:Docker的图形化管理工具,适合新手。
  • Portainer:轻量级的Docker管理UI,功能强大。

写在最后

Docker,是目前世界上,最流行、最强大的容器化技术。对于程序员来说,Docker,已经成为了必备技能。掌握了Docker,你就能更高效地搭建开发环境,部署应用,构建微服务架构。

学习Docker,不要死记硬背命令,而要理解它的基本原理。理解了镜像、容器、数据卷、网络等基本概念,很多命令,就自然而然地理解了。

另外,学习Docker,最好的方式,就是多实践。在实际项目中,多使用Docker,遇到问题,多查文档,多尝试,慢慢就熟练了。

2015年,Docker,还在快速发展中。Docker Compose、Docker Swarm等工具,还在不断完善;Kubernetes,也在2015年,由Google捐赠给CNCF,开始快速发展。容器化技术,正在改变软件开发和部署的方式。

"工欲善其事,必先利其器。"希望这篇文章,能帮你掌握Docker这个强大的工具,让你的开发工作,更加高效、更加顺畅。

最后,记住:Docker,只是工具。好的工具,能提升效率,但真正决定项目质量的,还是架构设计和代码本身。 不要为了用Docker而用Docker,要根据项目的实际需求,合理使用。

祝你,在Docker的世界里,游刃有余,高效开发!