Docker是近年来最热门的技术之一。它彻底改变了软件的开发测试和部署方式让应用的部署变得简单高效可移植。

作为一个程序员我很早就听说了Docker但是一直没有认真去学习和使用。直到最近项目需要做容器化部署我才花了时间认真学习了Docker并且在实际项目中进行了实践。

今天就来分享一下Docker容器化部署的实战经验从入门到精通希望能帮到大家。

什么是Docker

Docker是一个开源的应用容器引擎基于Go语言开发。它能让开发者打包他们的应用以及依赖包到一个可移植的容器中然后发布到任何流行的Linux机器,上。

Docker的核心思想是,"Build, Ship, and Run Any App, Anywhere",也就是构建运输运行任何应用在任何地方。

Docker的核心概念

  1. 镜像(Image):Docker镜像是一个只读的模板包含了运行应用所需的代码运行时库环境变量配置文件,等。镜像是用来创建Docker容器,的。
  2. 容器(Container):Docker容器是镜像的运行实例。容器是一个独立运行的应用或者一组应用以及它们的运行环境。
  3. 仓库(Repository):Docker仓库是用来存储镜像的地方。Docker Hub是官方的公共仓库里面有大量的镜像供大家使用。也可以搭建私有仓库。

Docker的优势

  1. 快速部署:Docker能让应用在几秒内启动大大缩短了部署时间。
  2. 环境一致:Docker把应用和它的依赖打包在一起保证了开发测试生产环境的一致性避免了,"在我机器上能跑",的问题。
  3. 资源高效:Docker容器共享主机的内核不需要像虚拟机那样每个都有独立的操作系统所以资源占用少启动,快。
  4. 易于迁移:Docker容器可以在任何支持Docker的机器上运行易于迁移和扩展。
  5. 版本管理:Docker镜像有版本管理可以方便地回滚到之前的版本。
  6. 隔离性好:Docker容器之间是隔离的一个容器的问题不会影响其他容器。

Docker的安装

在Ubuntu上安装Docker

在Ubuntu上安装Docker很简单可以通过官方脚本安装:

# 更新包索引
sudo apt-get update

# 安装必要的包
sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common

# 添加Docker官方GPG密钥
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

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

# 更新包索引
sudo apt-get update

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

# 启动Docker
sudo systemctl start docker

# 设置开机启动
sudo systemctl enable docker

# 验证安装
sudo docker --version

也可以用更简单的方式通过官方一键安装脚本:

curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun

在CentOS上安装Docker

在CentOS上安装Docker:

# 安装必要的包
sudo yum install -y yum-utils device-mapper-persistent-data lvm2

# 添加Docker仓库
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

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

# 启动Docker
sudo systemctl start docker

# 设置开机启动
sudo systemctl enable docker

# 验证安装
sudo docker --version

配置Docker镜像加速

由于国内访问Docker Hub比较慢建议配置镜像加速。可以使用阿里云腾讯云等的镜像加速器。

以阿里云为例:

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

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

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

Docker的基本命令

镜像相关命令

# 搜索镜像
docker search nginx

# 拉取镜像
docker pull nginx

# 拉取指定版本的镜像
docker pull nginx:1.19

# 查看本地镜像
docker images

# 删除镜像
docker rmi nginx

# 删除所有镜像
docker rmi $(docker images -q)

# 构建镜像
docker build -t myapp:1.0 .

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

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

容器相关命令

# 运行容器
docker run -d --name mynginx -p 80:80 nginx

# 查看运行中的容器
docker ps

# 查看所有容器(包括停止的)
docker ps -a

# 停止容器
docker stop mynginx

# 启动容器
docker start mynginx

# 重启容器
docker restart mynginx

# 删除容器
docker rm mynginx

# 删除所有停止的容器
docker container prune

# 进入容器
docker exec -it mynginx /bin/bash

# 查看容器日志
docker logs mynginx

# 实时查看容器日志
docker logs -f mynginx

# 查看容器详情
docker inspect mynginx

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

数据卷相关命令

# 创建数据卷
docker volume create myvolume

# 查看数据卷
docker volume ls

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

# 删除数据卷
docker volume rm myvolume

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

网络相关命令

# 查看网络
docker network ls

# 创建网络
docker network create mynetwork

# 查看网络详情
docker network inspect mynetwork

# 删除网络
docker network rm mynetwork

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

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

Dockerfile的编写

Dockerfile是用来构建Docker镜像的脚本文件。通过Dockerfile我们可以自动化地构建镜像。

Dockerfile的基本指令

  1. FROM:指定基础镜像。
  2. MAINTAINER:指定维护者信息(已废弃推荐用LABEL)。
  3. LABEL:添加元数据标签。
  4. RUN:执行命令。
  5. CMD:容器启动时执行的命令。
  6. ENTRYPOINT:容器启动时执行的命令(和CMD类似但不会被docker run的参数覆盖)。
  7. WORKDIR:设置工作目录。
  8. ENV:设置环境变量。
  9. COPY:复制文件到镜像,中。
  10. ADD:复制文件到镜像中(支持自动解压和URL)。
  11. EXPOSE:声明容器监听的端口。
  12. VOLUME:声明数据卷。
  13. USER:设置运行容器的用户。
  14. ARG:设置构建参数。
  15. ONBUILD:当该镜像被用作基础镜像时执行的命令。

一个简单的Node.js应用Dockerfile

# 基础镜像
FROM node:14-alpine

# 维护者
LABEL maintainer="yourname@example.com"

# 工作目录
WORKDIR /app

# 复制package.json
COPY package*.json ./

# 安装依赖
RUN npm install --production

# 复制应用代码
COPY . .

# 暴露端口
EXPOSE 3000

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

一个简单的PHP应用Dockerfile

# 基础镜像
FROM php:7.4-fpm-alpine

# 维护者
LABEL maintainer="yourname@example.com"

# 安装扩展
RUN docker-php-ext-install pdo pdo_mysql mysqli

# 工作目录
WORKDIR /var/www/html

# 复制应用代码
COPY . .

# 暴露端口
EXPOSE 9000

# 启动命令
CMD ["php-fpm"]

一个简单的Python应用Dockerfile

# 基础镜像
FROM python:3.8-slim

# 维护者
LABEL maintainer="yourname@example.com"

# 工作目录
WORKDIR /app

# 复制requirements.txt
COPY requirements.txt .

# 安装依赖
RUN pip install --no-cache-dir -r requirements.txt

# 复制应用代码
COPY . .

# 暴露端口
EXPOSE 5000

# 启动命令
CMD ["python", "app.py"]

Dockerfile的最佳实践

  1. 使用合适的基础镜像:选择官方的轻量级镜像比如alpine,slim等减小镜像体积。
  2. 合理利用缓存:把不常变化的指令放在前面常变化的指令放在后面充分利用构建缓存。
  3. 合并RUN指令:把多个RUN指令合并成一个减少镜像层数减小镜像体积。
  4. 清理不需要的文件:在RUN指令中安装完依赖后清理缓存和不需要的文件减小镜像体积。
  5. 使用.dockerignore:创建,.dockerignore文件排除不需要的文件减小构建上下文加快构建速度。
  6. 多阶段构建:使用多阶段构建把编译环境和运行环境分开减小最终镜像体积。
  7. 不要用latest标签:使用具体的版本号而不是latest保证构建的可复现性。
  8. 以非root用户运行:创建普通用户以非root用户运行容器提高安全性。

Docker Compose的使用

Docker Compose是用来定义和运行多容器Docker应用的工具。通过一个docker-compose.yml文件我们可以配置应用的所有服务然后用一条命令启动所有服务。

安装Docker Compose

# 下载Docker Compose
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

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

# 验证安装
docker-compose --version

docker-compose.yml的基本结构

version: '3'

services:
  web:
    image: nginx:latest
    ports:
      - "80:80"
    volumes:
      - ./html:/usr/share/nginx/html
    depends_on:
      - php
    networks:
      - mynetwork

  php:
    build: ./php
    volumes:
      - ./html:/var/www/html
    networks:
      - mynetwork

  mysql:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: mydb
    volumes:
      - mysql_data:/var/lib/mysql
    networks:
      - mynetwork

volumes:
  mysql_data:

networks:
  mynetwork:

Docker Compose的常用命令

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

# 停止所有服务
docker-compose down

# 查看服务状态
docker-compose ps

# 查看服务日志
docker-compose logs

# 实时查看服务日志
docker-compose logs -f

# 构建或重新构建服务
docker-compose build

# 重启服务
docker-compose restart

# 进入服务容器
docker-compose exec web /bin/bash

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

一个完整的LNMP环境docker-compose.yml

version: '3'

services:
  nginx:
    image: nginx:1.19-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
    networks:
      - lnmp-network

  php:
    build: ./php
    container_name: lnmp-php
    volumes:
      - ./www:/var/www/html
      - ./php/php.ini:/usr/local/etc/php/php.ini
      - ./logs/php:/var/log/php
    depends_on:
      - mysql
      - redis
    networks:
      - lnmp-network

  mysql:
    image: mysql:5.7
    container_name: lnmp-mysql
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: mydb
      TZ: Asia/Shanghai
    volumes:
      - mysql_data:/var/lib/mysql
      - ./mysql/my.cnf:/etc/mysql/my.cnf
      - ./logs/mysql:/var/log/mysql
    networks:
      - lnmp-network

  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
    networks:
      - lnmp-network

volumes:
  mysql_data:
  redis_data:

networks:
  lnmp-network:
    driver: bridge

Docker容器化部署实战

下面我以一个PHP博客系统为例演示如何用Docker进行容器化部署。

项目结构

blog/
├── docker-compose.yml
├── nginx/
│   ├── nginx.conf
│   └── conf.d/
│       └── default.conf
├── php/
│   ├── Dockerfile
│   └── php.ini
├── mysql/
│   └── my.cnf
├── redis/
│   └── redis.conf
├── www/
│   └── (博客代码)
└── logs/
    ├── nginx/
    ├── php/
    └── mysql/

编写docker-compose.yml

version: '3'

services:
  nginx:
    image: nginx:1.19-alpine
    container_name: blog-nginx
    restart: always
    ports:
      - "80:80"
    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
    networks:
      - blog-network

  php:
    build: ./php
    container_name: blog-php
    restart: always
    volumes:
      - ./www:/var/www/html
      - ./php/php.ini:/usr/local/etc/php/php.ini
      - ./logs/php:/var/log/php
    depends_on:
      - mysql
      - redis
    networks:
      - blog-network

  mysql:
    image: mysql:5.7
    container_name: blog-mysql
    restart: always
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: blog
      TZ: Asia/Shanghai
    volumes:
      - mysql_data:/var/lib/mysql
      - ./mysql/my.cnf:/etc/mysql/my.cnf
      - ./logs/mysql:/var/log/mysql
    networks:
      - blog-network

  redis:
    image: redis:6-alpine
    container_name: blog-redis
    restart: always
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
      - ./redis/redis.conf:/etc/redis/redis.conf
    command: redis-server /etc/redis/redis.conf
    networks:
      - blog-network

volumes:
  mysql_data:
  redis_data:

networks:
  blog-network:
    driver: bridge

编写PHP的Dockerfile

FROM php:7.4-fpm-alpine

LABEL maintainer="yourname@example.com"

# 安装依赖
RUN apk add --no-cache \
    freetype \
    libjpeg-turbo \
    libpng \
    libzip \
    && apk add --no-cache --virtual .build-deps \
    freetype-dev \
    libjpeg-turbo-dev \
    libpng-dev \
    libzip-dev \
    && docker-php-ext-configure gd \
    --with-freetype \
    --with-jpeg \
    && docker-php-ext-install -j$(nproc) \
    gd \
    pdo \
    pdo_mysql \
    mysqli \
    zip \
    opcache \
    && apk del .build-deps

# 安装Redis扩展
RUN apk add --no-cache --virtual .build-deps \
    autoconf \
    g++ \
    make \
    && pecl install redis \
    && docker-php-ext-enable redis \
    && apk del .build-deps

# 设置工作目录
WORKDIR /var/www/html

# 暴露端口
EXPOSE 9000

# 启动命令
CMD ["php-fpm"]

编写Nginx配置

nginx.conf:

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';

    access_log /var/log/nginx/access.log main;

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    client_max_body_size 20m;

    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml;

    include /etc/nginx/conf.d/*.conf;
}

conf.d/default.conf:

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

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

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

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

启动服务

# 进入项目目录
cd blog

# 构建并启动所有服务
docker-compose up -d --build

# 查看服务状态
docker-compose ps

# 查看日志
docker-compose logs -f

部署应用

  1. 把博客代码放到www目录,下。
  2. 修改数据库配置主机名填mysql端口3306用户名root密码root数据库名blog。
  3. 修改Redis配置主机名填redis端口6379。
  4. 访问http://服务器IP即可看到博客。

备份和恢复

备份数据库:

docker exec blog-mysql mysqldump -uroot -proot blog > backup.sql

恢复数据库:

docker exec -i blog-mysql mysql -uroot -proot blog < backup.sql

备份数据卷:

# 备份MySQL数据卷
docker run --rm -v mysql_data:/data -v $(pwd):/backup alpine tar czf /backup/mysql_backup.tar.gz -C /data .

# 备份Redis数据卷
docker run --rm -v redis_data:/data -v $(pwd):/backup alpine tar czf /backup/redis_backup.tar.gz -C /data .

Docker的安全最佳实践

  1. 使用官方镜像:尽量使用官方的镜像避免使用来源不明的镜像。
  2. 定期更新镜像:定期更新基础镜像和应用镜像修复安全漏洞。
  3. 以非root用户运行:在Dockerfile中创建普通用户以非root用户运行容器减少安全风险。
  4. 不要在镜像中存储敏感信息:不要把密码密钥等敏感信息写在Dockerfile或镜像中使用环境变量或密钥管理工具。
  5. 限制容器资源:使用,--memory,--cpus等参数限制容器的资源使用防止单个容器占用过多资源。
  6. 只读文件系统:使用,--read-only参数让容器的文件系统只读减少被篡改的风险。
  7. 禁用不需要的能力:使用,--cap-drop禁用不需要的Linux capabilities减少攻击面。
  8. 使用安全扫描工具:使用Trivy,Clair等工具扫描镜像的安全漏洞。
  9. 不要暴露不必要的端口:只暴露需要的端口不要把数据库等内部服务的端口暴露到公网。
  10. 使用Docker Content Trust:启用Docker Content Trust验证镜像的签名确保镜像没有被篡改。

常见问题与解决方法

1. 容器启动后立即退出

可能原因:

  • 容器的主进程执行完就退出了。
  • 配置错误导致进程启动失败。

解决方法:

  • 查看容器日志,docker logs 容器名,找错误信息。
  • 确保容器的主进程是前台运行的不要用后台运行。
  • 用,docker run -it 镜像名 /bin/bash,进入容器手动执行启动命令调试。

2. 端口映射不生效

可能原因:

  • 端口被占用。
  • 容器内的服务没有监听正确的端口。
  • 防火墙阻止了端口。

解决方法:

  • 用,netstat -tlnp,查看端口是否被占用。
  • 进入容器用,netstat -tlnp,查看服务是否监听正确的端口。
  • 检查防火墙规则开放对应端口。

3. 容器之间无法通信

可能原因:

  • 容器不在同一个网络。
  • 用了localhost而不是容器名。

解决方法:

  • 确保需要通信的容器在同一个自定义网络。
  • 容器之间通信用容器名或服务名作为主机名不要用localhost或127.0.0.1。

4. 数据丢失

可能原因:

  • 没有用数据卷数据存在容器的可写层容器删除后数据丢失。

解决方法:

  • 重要的数据一定要用数据卷或绑定挂载持久化。
  • 定期备份数据卷。

5. 镜像构建慢

可能原因:

  • 构建上下文太大。
  • 没有利用缓存。
  • 下载依赖,慢。

解决方法:

  • 创建,.dockerignore排除不需要的文件。
  • 合理安排Dockerfile指令顺序利用缓存。
  • 配置国内镜像源加速下载。

写在最后

Docker是一个非常强大的工具它彻底改变了软件的开发测试和部署方式。掌握Docker已经成为了现代程序员的必备技能。

这篇文章从Docker的基本概念安装基本命令Dockerfile,Docker Compose到实战部署安全最佳实践常见问题做了一个比较全面的介绍。希望能帮到大家快速入门Docker。

当然Docker的内容还有很多比如Docker Swarm,Kubernetes等容器编排工具这篇文章没有涉及。如果大家感兴趣可以后续再深入学习。

最后记住:实践出真知。 看再多的教程不如自己动手实践一次。希望大家都能动手试试Docker感受它的强大和便捷。

祝大家都能掌握Docker让自己的开发和部署工作更高效更轻松。