Docker从入门到放弃?不存在的!这份实战指南让你秒变容器老司机
最近总有朋友问我Docker到底是个啥,怎么用,感觉很高大上但又不知道从哪里下手。说实话,我刚开始接触Docker的时候也是一脸懵逼,各种概念搞得头大。不过用了几年下来,现在回头看,Docker真的是个好东西,能解决很多实际问题。
今天就来聊聊Docker的实际使用,不讲那些虚头巴脑的理论,直接上干货。我会把自己这几年踩过的坑、用过的技巧都分享出来,希望能帮到大家。
什么是Docker,为什么要用它
Docker说白了就是一个容器技术,可以把应用程序和它的运行环境打包在一起。你可以理解为一个轻量级的虚拟机,但比虚拟机要快很多。
我记得以前部署应用的时候,经常遇到"在我电脑上能跑啊"这种问题。开发环境Python 3.8,测试环境Python 3.7,生产环境又是3.9,各种版本冲突搞得人头疼。有了Docker之后,这些问题基本就不存在了,因为环境都打包好了,到哪里都一样。
还有一个好处就是资源利用率高。以前一台服务器可能只跑一个应用,现在可以跑十几个容器,每个容器都是独立的,互不影响。
安装Docker
安装Docker其实很简单,官网都有详细的文档。不过我还是说几个注意点。
在Ubuntu上安装:
# 更新包索引
sudo apt update
# 安装必要的包
sudo apt install apt-transport-https ca-certificates curl gnupg lsb-release
# 添加Docker官方GPG密钥
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# 设置稳定版仓库
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# 安装Docker Engine
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io安装完成后记得把当前用户加入docker组,这样就不用每次都sudo了:
sudo usermod -aG docker $USER然后重新登录一下,或者执行newgrp docker。
CentOS的安装过程类似,就是包管理器换成yum。Windows和Mac的话直接下载Docker Desktop就行了,图形界面操作很方便。
Docker的基本概念
在开始实际操作之前,先理解几个核心概念。
镜像(Image):可以理解为一个模板,包含了运行应用所需的所有东西。比如一个Ubuntu镜像就包含了Ubuntu系统的基本文件。
容器(Container):镜像运行起来就是容器。一个镜像可以创建多个容器,就像一个类可以实例化多个对象一样。
仓库(Repository):存放镜像的地方,Docker Hub是最大的公共仓库。
这三个概念搞清楚了,后面的操作就好理解了。
常用Docker命令
镜像相关命令
查看本地镜像:
docker images
拉取镜像:
docker pull nginx:latest
docker pull ubuntu:20.04
删除镜像:
docker rmi nginx:latest构建镜像:
docker build -t myapp:v1.0 .容器相关命令
运行容器:
# 最基本的运行
docker run hello-world
# 交互式运行
docker run -it ubuntu:20.04 /bin/bash
# 后台运行
docker run -d nginx:latest
# 端口映射
docker run -d -p 8080:80 nginx:latest查看容器:
# 查看正在运行的容器
docker ps
# 查看所有容器(包括停止的)
docker ps -a
停止和启动容器:
docker stop container_id
docker start container_id
docker restart container_id进入容器:
docker exec -it container_id /bin/bash删除容器:
docker rm container_id查看容器日志:
docker logs container_id
docker logs -f container_id # 实时查看实战案例:部署一个Web应用
说了这么多理论,来个实际例子。我们部署一个简单的Flask应用。
首先创建一个Flask应用,文件名app.py:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return '<h1>Hello from Docker!</h1>'
@app.route('/health')
def health():
return {'status': 'ok'}
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)创建requirements.txt:
Flask==2.3.3然后写Dockerfile:
# 使用Python 3.9作为基础镜像
FROM python:3.9-slim
# 设置工作目录
WORKDIR /app
# 复制requirements文件
COPY requirements.txt .
# 安装依赖
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY app.py .
# 暴露端口
EXPOSE 5000
# 运行应用
CMD ["python", "app.py"]构建镜像:
docker build -t flask-app:v1.0 .运行容器:
docker run -d -p 5000:5000 --name my-flask-app flask-app:v1.0现在访问http://localhost:5000就能看到应用了。
这个例子看起来简单,但包含了Docker的核心用法。在实际项目中,可能还需要考虑数据持久化、环境变量配置等问题。
数据持久化和卷(Volume)
容器默认是无状态的,容器删除后数据就没了。但很多时候我们需要持久化数据,比如数据库文件、日志文件等。
Docker提供了几种数据持久化的方式:
绑定挂载(Bind Mount)
直接把主机的目录挂载到容器里:
docker run -d -v /host/path:/container/path nginx:latest比如运行一个MySQL容器,把数据目录挂载到主机:
docker run -d \
--name mysql-server \
-e MYSQL_ROOT_PASSWORD=mypassword \
-v /opt/mysql-data:/var/lib/mysql \
-p 3306:3306 \
mysql:8.0这样即使容器删除了,数据库文件还在主机的/opt/mysql-data目录里。
Docker卷(Volume)
这是Docker推荐的方式:
# 创建卷
docker volume create mydata
# 使用卷
docker run -d -v mydata:/data nginx:latest
# 查看卷
docker volume ls
# 查看卷详情
docker volume inspect mydata卷的好处是由Docker管理,不用担心路径问题,而且可以在容器间共享。
临时文件系统(tmpfs)
把数据存储在内存中,容器停止后数据就没了:
docker run -d --tmpfs /tmp nginx:latest这种方式适合存储临时数据,比如缓存文件。
Docker网络详解
Docker的网络功能很强大,也是很多人觉得复杂的地方。我刚开始学的时候也被搞得晕头转向,不过理解了原理之后就清晰多了。
Docker网络架构
Docker使用Linux的网络命名空间来实现容器网络隔离。每个容器都有自己的网络栈,包括网卡、路由表、防火墙规则等。
Docker在宿主机上创建了一个虚拟网桥docker0,默认情况下所有容器都连接到这个网桥上。容器之间可以通过这个网桥进行通信。
默认网络模式
Docker提供了几种网络模式:
bridge模式(默认):
容器连接到docker0网桥,有自己的IP地址,可以和其他容器通信。
docker run -d --name web nginx:latesthost模式:
容器直接使用宿主机的网络,没有网络隔离。
docker run -d --network host nginx:latest这种模式下容器的网络性能最好,但失去了隔离性。
none模式:
容器没有网络接口,完全隔离。
docker run -d --network none nginx:latestcontainer模式:
容器共享另一个容器的网络。
docker run -d --name web nginx:latest
docker run -d --network container:web busybox:latest查看默认网络
Docker安装后会创建几个默认网络:
docker network ls通常会看到这些网络:
- bridge:默认网桥网络
- host:主机网络
- none:无网络

查看网络详情:
docker network inspect bridge这个命令会显示网络的配置信息,包括子网、网关、连接的容器等。

自定义网络
自定义网络是Docker网络的精髓,可以实现更灵活的网络配置。
创建bridge网络
# 创建自定义网络
docker network create mynetwork
# 指定子网和网关
docker network create --driver bridge \
--subnet=172.20.0.0/16 \
--ip-range=172.20.240.0/20 \
--gateway=172.20.0.1 \
mynetwork2使用自定义网络
# 运行容器时指定网络
docker run -d --network mynetwork --name web nginx:latest
docker run -d --network mynetwork --name db mysql:8.0
# 给运行中的容器连接网络
docker network connect mynetwork existing_container自定义网络的优势
在自定义网络中,容器可以通过容器名相互访问,这是默认bridge网络没有的功能:
# 创建网络和容器
docker network create webapp
docker run -d --network webapp --name database mysql:8.0
docker run -d --network webapp --name backend node-app:latest
docker run -d --network webapp --name frontend nginx:latest
# backend容器可以通过"database"这个主机名访问数据库
# frontend容器可以通过"backend"访问后端API这个功能叫做自动服务发现,非常实用。
网络连接和断开
容器可以同时连接多个网络:
# 创建两个网络
docker network create frontend
docker network create backend
# 容器连接到多个网络
docker run -d --network frontend --name web nginx:latest
docker network connect backend web
# 断开网络连接
docker network disconnect frontend web这样可以实现更复杂的网络拓扑,比如三层架构中的网络隔离。
端口映射详解
端口映射让外部可以访问容器内的服务,有几种方式:
基本端口映射
# 映射单个端口:宿主机8080端口映射到容器80端口
docker run -d -p 8080:80 nginx:latest
# 映射多个端口
docker run -d -p 8080:80 -p 8443:443 nginx:latest
# 映射到指定IP
docker run -d -p 127.0.0.1:8080:80 nginx:latest
# 随机映射端口
docker run -d -P nginx:latestUDP端口映射
# TCP端口(默认)
docker run -d -p 53:53 dns-server:latest
# UDP端口
docker run -d -p 53:53/udp dns-server:latest
# 同时映射TCP和UDP
docker run -d -p 53:53/tcp -p 53:53/udp dns-server:latest查看端口映射
# 查看容器端口映射
docker port container_name
# 查看所有容器的端口
docker ps --format "table {{.Names}}\t{{.Ports}}"容器间通信实例
来个实际例子,部署一个Web应用,包含前端、后端、数据库:
# 创建自定义网络
docker network create webapp --subnet=172.18.0.0/16
# 启动数据库容器
docker run -d \
--name database \
--network webapp \
--ip 172.18.0.10 \
-e MYSQL_ROOT_PASSWORD=rootpass \
-e MYSQL_DATABASE=myapp \
mysql:8.0
# 启动后端API容器
docker run -d \
--name api \
--network webapp \
--ip 172.18.0.20 \
-e DATABASE_HOST=database \
-e DATABASE_PORT=3306 \
my-api:latest
# 启动前端容器
docker run -d \
--name frontend \
--network webapp \
--ip 172.18.0.30 \
-p 80:80 \
-e API_HOST=api \
-e API_PORT=3000 \
my-frontend:latest在这个例子中:
- 数据库容器不需要暴露端口到宿主机,只在内部网络中通信
- API容器可以通过"database"主机名访问数据库
- 前端容器可以通过"api"主机名访问后端
- 只有前端容器暴露端口给外部访问
网络故障排查
当容器网络出问题时,可以用这些方法排查:
检查容器网络配置
# 查看容器的网络配置
docker inspect container_name | grep -A 20 "NetworkSettings"
# 查看容器IP地址
docker inspect container_name | grep IPAddress进入容器测试网络
# 进入容器
docker exec -it container_name /bin/bash
# 测试网络连通性
ping another_container
telnet database 3306
curl http://api:3000/health
# 查看网络接口
ip addr show
route -n在容器内安装网络工具
# Debian/Ubuntu容器
apt-get update && apt-get install -y iputils-ping curl telnet net-tools
# Alpine容器
apk add --no-cache iputils curl
# CentOS容器
yum install -y iputils curl telnet net-tools宿主机网络检查
# 查看docker网桥
ip addr show docker0
brctl show docker0
# 查看iptables规则
iptables -L -n
iptables -t nat -L -n
# 查看网络命名空间
ip netns list跨主机网络
当应用需要在多台服务器上运行时,就需要跨主机网络了。Docker提供了几种方案:
Docker Swarm网络
# 初始化Swarm集群
docker swarm init --advertise-addr 192.168.1.100
# 在其他节点加入集群
docker swarm join --token <token> 192.168.1.100:2377
# 创建overlay网络
docker network create -d overlay myoverlay
# 部署服务到overlay网络
docker service create --network myoverlay --name web nginx:latestOverlay网络让不同主机上的容器可以直接通信,就像在同一台机器上一样。
第三方网络插件
比如Calico、Flannel、Weave等,提供更高级的网络功能。
网络安全
容器网络安全也很重要,几个建议:
网络隔离
# 创建独立的网络,避免容器间不必要的通信
docker network create --internal backend-only
docker run -d --network backend-only database:latest防火墙规则
# Docker会自动添加iptables规则,但可以手动调整
iptables -I DOCKER-USER -s 172.17.0.0/16 -d 192.168.1.0/24 -j DROP使用非特权端口
# 避免使用特权端口(1-1024)
docker run -d -p 8080:80 nginx:latest # 好
docker run -d -p 80:80 nginx:latest # 不推荐网络性能优化
选择合适的网络模式
- 对性能要求极高的应用可以使用host模式
- 一般应用使用bridge模式就够了
- 需要网络隔离的用自定义网络
调整网络参数
# 修改docker daemon配置
cat > /etc/docker/daemon.json << EOF
{
"bip": "172.17.0.1/16",
"mtu": 1500,
"default-address-pools": [
{
"base": "172.80.0.0/12",
"size": 24
}
]
}
EOF监控网络流量
# 查看容器网络统计
docker stats --format "table {{.Container}}\t{{.NetIO}}"
# 使用iftop监控网络流量
docker exec -it container_name iftop环境变量和配置
很多应用需要通过环境变量进行配置,Docker提供了几种方式:
运行时指定
docker run -d -e MYSQL_ROOT_PASSWORD=mypassword mysql:8.0
docker run -d -e NODE_ENV=production -e PORT=3000 node-app:latest使用env文件
创建.env文件:
MYSQL_ROOT_PASSWORD=mypassword
MYSQL_DATABASE=myapp
MYSQL_USER=appuser
MYSQL_PASSWORD=apppassword然后运行:
docker run -d --env-file .env mysql:8.0这种方式比较安全,敏感信息不会出现在命令行里。
在Dockerfile中设置默认值
FROM node:16
ENV NODE_ENV=production
ENV PORT=3000
# ...Docker Compose:多容器应用的救星
当应用变复杂,需要多个容器协同工作时,一个个手动启动容器就很麻烦了。Docker Compose就是来解决这个问题的。
安装Docker Compose
现在的Docker Desktop都自带了Compose,Linux上可能需要单独安装:
sudo curl -L "https://github.com/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.yml
比如一个典型的Web应用,需要Web服务器、数据库、Redis缓存:
version: '3.8'
services:
web:
build: .
ports:
- "5000:5000"
environment:
- DATABASE_URL=mysql://user:password@db:3306/myapp
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
volumes:
- ./logs:/app/logs
networks:
- frontend
- backend
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: myapp
MYSQL_USER: user
MYSQL_PASSWORD: password
volumes:
- mysql_data:/var/lib/mysql
networks:
- backend
redis:
image: redis:7-alpine
networks:
- backend
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- web
networks:
- frontend
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true
volumes:
mysql_data:这个例子展示了网络分层:
- frontend网络连接nginx和web服务
- backend网络连接web、db和redis
- backend网络设置为internal,外部无法直接访问
使用Compose命令
# 启动所有服务
docker-compose up -d
# 查看服务状态
docker-compose ps
# 查看日志
docker-compose logs web
docker-compose logs -f # 实时查看所有服务日志
# 停止服务
docker-compose stop
# 停止并删除容器
docker-compose down
# 重新构建并启动
docker-compose up -d --build用了Compose之后,管理多容器应用就轻松多了。一个命令就能启动整个应用栈,而且服务之间的依赖关系也处理得很好。
实际生产环境的最佳实践
镜像优化
镜像大小直接影响部署速度,所以要尽量优化:
使用多阶段构建:
# 构建阶段
FROM node:16 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# 运行阶段
FROM node:16-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]选择合适的基础镜像:
- alpine版本通常比较小
- slim版本是精简版,比完整版小很多
- 如果不需要包管理器,可以用distroless镜像
清理不必要的文件:
RUN apt-get update && apt-get install -y \
package1 \
package2 \
&& rm -rf /var/lib/apt/lists/*安全考虑
不要用root用户运行应用:
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
USER nextjs扫描镜像漏洞:
docker scan myapp:latest使用.dockerignore:
node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.nyc_output
coverage
.nyc_output健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1在Compose中:
services:
web:
build: .
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s日志管理
Docker默认的json-file日志驱动会让日志文件越来越大,生产环境建议配置日志轮转:
docker run -d \
--log-driver json-file \
--log-opt max-size=10m \
--log-opt max-file=3 \
nginx:latest或者使用外部日志系统,比如ELK Stack。
监控和调试
查看容器资源使用情况
# 实时查看
docker stats
# 查看特定容器
docker stats container_name进入容器调试
# 进入运行中的容器
docker exec -it container_name /bin/bash
# 如果没有bash,试试sh
docker exec -it container_name /bin/sh查看容器详细信息
docker inspect container_name这个命令会输出容器的所有配置信息,包括网络、挂载点、环境变量等,调试时很有用。
容器内安装调试工具
有时候需要在容器内安装一些调试工具:
# 在Debian/Ubuntu容器内
apt-get update && apt-get install -y curl vim net-tools
# 在Alpine容器内
apk add --no-cache curl vim不过这种方式只是临时的,容器重启后就没了。
常见问题和解决方案
容器启动失败
首先查看日志:
docker logs container_name常见原因:
- 端口被占用
- 环境变量配置错误
- 挂载路径不存在
- 权限问题
容器内时间不对
容器默认使用UTC时间,如果需要本地时间:
docker run -d -v /etc/localtime:/etc/localtime:ro myapp:latest或者设置时区环境变量:
docker run -d -e TZ=Asia/Shanghai myapp:latest镜像拉取慢
国内网络环境下,从Docker Hub拉取镜像可能很慢,可以配置镜像加速器。
编辑/etc/docker/daemon.json:
{
"registry-mirrors": [
"https://docker.mirrors.ustc.edu.cn",
"https://hub-mirror.c.163.com"
]
}然后重启Docker服务:
sudo systemctl restart docker容器间通信问题
确保容器在同一个网络中,或者使用link(虽然已经deprecated):
docker run -d --name db mysql:8.0
docker run -d --link db:database web-app:latest不过建议还是用自定义网络,更灵活。
数据丢失问题
记住容器是无状态的,重要数据一定要持久化。数据库、配置文件、日志文件都要挂载到主机或者Docker卷。
Docker在CI/CD中的应用
Docker在持续集成和持续部署中发挥了重要作用。
GitLab CI示例
stages:
- build
- test
- deploy
variables:
DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
build:
stage: build
script:
- docker build -t $DOCKER_IMAGE .
- docker push $DOCKER_IMAGE
test:
stage: test
script:
- docker run --rm $DOCKER_IMAGE npm test
deploy:
stage: deploy
script:
- docker pull $DOCKER_IMAGE
- docker stop myapp || true
- docker rm myapp || true
- docker run -d --name myapp -p 80:3000 $DOCKER_IMAGE
only:
- mainGitHub Actions示例
name: Build and Deploy
on:
push:
branches: [ main ]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build Docker image
run: docker build -t myapp:${{ github.sha }} .
- name: Run tests
run: docker run --rm myapp:${{ github.sha }} npm test
- name: Deploy
run: |
docker stop myapp || true
docker rm myapp || true
docker run -d --name myapp -p 80:3000 myapp:${{ github.sha }}这样每次代码提交后,就会自动构建镜像、运行测试、部署应用。
性能优化技巧
减少镜像层数
每个RUN、COPY、ADD指令都会创建一个新的镜像层,尽量合并:
# 不好的做法
RUN apt-get update
RUN apt-get install -y package1
RUN apt-get install -y package2
# 好的做法
RUN apt-get update && apt-get install -y \
package1 \
package2 \
&& rm -rf /var/lib/apt/lists/*利用构建缓存
Docker会缓存镜像层,把变化频繁的指令放在后面:
# 先复制依赖文件
COPY package.json package-lock.json ./
RUN npm ci
# 再复制源代码
COPY . .这样源代码变化时,依赖安装的缓存还能用。
使用.dockerignore
类似.gitignore,避免把不必要的文件复制到镜像中:
node_modules
.git
.gitignore
README.md
Dockerfile
.dockerignore选择合适的基础镜像
- 开发环境可以用完整版镜像,方便调试
- 生产环境用精简版,减少攻击面和镜像大小
- 如果追求极致性能,可以用scratch或distroless
Docker网络高级配置
刚才讲了基础的网络知识,现在来看看一些高级配置。
MacVLAN网络
MacVLAN可以让容器直接获得物理网络的IP地址,就像虚拟机一样:
# 创建MacVLAN网络
docker network create -d macvlan \
--subnet=192.168.1.0/24 \
--gateway=192.168.1.1 \
-o parent=eth0 \
macvlan-net
# 运行容器
docker run -d --network macvlan-net --ip=192.168.1.100 nginx:latest这种方式的好处是容器可以直接和物理网络中的设备通信,不需要端口映射。但缺点是需要网络管理员分配IP地址,而且不是所有的网络环境都支持。
IPvlan网络
IPvlan类似MacVLAN,但使用同一个MAC地址:
docker network create -d ipvlan \
--subnet=192.168.1.0/24 \
--gateway=192.168.1.1 \
-o parent=eth0 \
-o ipvlan_mode=l2 \
ipvlan-netIPvlan有两种模式:
- L2模式:类似MacVLAN,但共享MAC地址
- L3模式:路由模式,需要配置路由规则
网络别名
在自定义网络中,可以给容器设置别名:
docker network create mynet
docker run -d --network mynet --network-alias db --network-alias database mysql:8.0这样其他容器可以通过"db"或"database"来访问这个MySQL容器。
网络插件
Docker支持第三方网络插件,比如:
Weave:
# 安装Weave
sudo curl -L git.io/weave -o /usr/local/bin/weave
sudo chmod a+x /usr/local/bin/weave
# 启动Weave
weave launch
eval $(weave env)
# 运行容器
docker run -d --name web nginx:latestCalico:
# 下载Calico
curl -O -L https://github.com/projectcalico/calicoctl/releases/download/v3.24.0/calicoctl
chmod +x calicoctl
# 配置网络策略
calicoctl apply -f network-policy.yaml这些插件提供了更高级的网络功能,比如网络策略、跨数据中心网络等。
Docker存储驱动
Docker支持多种存储驱动,不同的驱动有不同的特点:
查看当前存储驱动
docker info | grep "Storage Driver"常见存储驱动
overlay2(推荐):
- 性能好,功能完整
- 支持所有Docker功能
- 大部分Linux发行版的默认选择
aufs:
- 老的存储驱动
- 在一些老系统上还在用
- 性能不如overlay2
devicemapper:
- 使用块设备
- 适合企业级存储
- 配置复杂
btrfs:
- 支持快照和子卷
- 适合需要高级存储功能的场景
配置存储驱动
编辑/etc/docker/daemon.json:
{
"storage-driver": "overlay2",
"storage-opts": [
"overlay2.override_kernel_check=true"
]
}然后重启Docker服务。
Docker安全最佳实践
安全是生产环境必须考虑的问题,Docker提供了很多安全功能。
用户命名空间
默认情况下,容器内的root用户就是宿主机的root用户,这很危险。用户命名空间可以把容器内的root映射到宿主机的普通用户:
# 配置用户命名空间
echo 'dockremap:165536:65536' >> /etc/subuid
echo 'dockremap:165536:65536' >> /etc/subgid
# 修改Docker配置
echo '{"userns-remap": "default"}' > /etc/docker/daemon.json
systemctl restart docker限制容器资源
防止容器消耗过多资源:
# 限制内存和CPU
docker run -d --memory=512m --cpus=1.0 nginx:latest
# 限制磁盘IO
docker run -d --device-read-bps /dev/sda:1mb --device-write-bps /dev/sda:1mb nginx:latest使用非特权容器
# 以非特权用户运行
docker run -d --user 1000:1000 nginx:latest
# 禁用特权模式
docker run -d --security-opt no-new-privileges nginx:latestAppArmor和SELinux
如果系统支持,可以使用AppArmor或SELinux加强安全:
# 使用AppArmor配置文件
docker run -d --security-opt apparmor:docker-default nginx:latest
# 使用SELinux标签
docker run -d --security-opt label:type:container_t nginx:latest扫描镜像漏洞
定期扫描镜像,发现安全漏洞:
# 使用Docker官方扫描工具
docker scan nginx:latest
# 使用Clair扫描器
docker run -d --name clair-db postgres:latest
docker run -d --name clair --link clair-db:postgres quay.io/coreos/clair:latest签名和验证镜像
使用Docker Content Trust验证镜像:
# 启用内容信任
export DOCKER_CONTENT_TRUST=1
# 推送签名镜像
docker push myregistry.com/myimage:latest
# 拉取时自动验证
docker pull myregistry.com/myimage:latestDocker Registry私有仓库
在企业环境中,通常需要搭建私有镜像仓库。
搭建简单的Registry
# 运行Registry容器
docker run -d \
-p 5000:5000 \
--restart=always \
--name registry \
-v registry-data:/var/lib/registry \
registry:2
# 推送镜像到私有仓库
docker tag myapp:latest localhost:5000/myapp:latest
docker push localhost:5000/myapp:latest
# 从私有仓库拉取
docker pull localhost:5000/myapp:latest配置HTTPS和认证
生产环境的Registry需要HTTPS和认证:
# 生成证书
mkdir certs
openssl req -newkey rsa:4096 -nodes -sha256 -keyout certs/domain.key -x509 -days 365 -out certs/domain.crt
# 生成认证文件
mkdir auth
docker run --entrypoint htpasswd registry:2 -Bbn admin password > auth/htpasswd
# 运行带认证的Registry
docker run -d \
-p 443:5000 \
--restart=always \
--name secure-registry \
-v $(pwd)/certs:/certs \
-v $(pwd)/auth:/auth \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \
-e REGISTRY_HTTP_TLS_PRIVATE_KEY=/certs/domain.key \
-e REGISTRY_AUTH=htpasswd \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
-e REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" \
registry:2
# 登录私有仓库
docker login myregistry.comHarbor企业级仓库
Harbor是VMware开源的企业级Docker仓库:
# 下载Harbor
wget https://github.com/goharbor/harbor/releases/download/v2.8.0/harbor-offline-installer-v2.8.0.tgz
tar xvf harbor-offline-installer-v2.8.0.tgz
# 配置Harbor
cd harbor
cp harbor.yml.tmpl harbor.yml
vim harbor.yml # 修改配置
# 安装Harbor
sudo ./install.shHarbor提供了Web界面、用户管理、项目管理、漏洞扫描等企业级功能。
Docker多架构镜像
现在有越来越多的ARM服务器,多架构镜像变得重要了。
构建多架构镜像
# 创建builder实例
docker buildx create --name mybuilder --use
docker buildx inspect --bootstrap
# 构建多架构镜像
docker buildx build \
--platform linux/amd64,linux/arm64,linux/arm/v7 \
-t myapp:latest \
--push .使用manifest
# 创建manifest
docker manifest create myapp:latest \
myapp:amd64 \
myapp:arm64 \
myapp:armv7
# 推送manifest
docker manifest push myapp:latest在Dockerfile中处理架构差异
FROM --platform=$BUILDPLATFORM golang:1.19 AS builder
ARG TARGETPLATFORM
ARG BUILDPLATFORM
ARG TARGETOS
ARG TARGETARCH
WORKDIR /app
COPY . .
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o myapp
FROM alpine:latest
RUN apk --no-cache add ca-certificates
COPY --from=builder /app/myapp /usr/local/bin/
CMD ["myapp"]Docker和Kubernetes集成
虽然Kubernetes现在支持多种容器运行时,但Docker仍然是最常用的。
准备镜像给Kubernetes使用
# 使用非root用户
FROM node:16-alpine
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN chown -R nextjs:nodejs /app
USER nextjs
EXPOSE 3000
CMD ["node", "server.js"]Kubernetes部署文件
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myregistry.com/myapp:v1.0
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: production
resources:
limits:
memory: 512Mi
cpu: 500m
requests:
memory: 256Mi
cpu: 250m
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5镜像拉取策略
spec:
containers:
- name: myapp
image: myapp:latest
imagePullPolicy: Always # 总是拉取最新镜像
# imagePullPolicy: IfNotPresent # 本地没有才拉取
# imagePullPolicy: Never # 从不拉取,只用本地镜像容器监控和日志
生产环境中,监控和日志收集很重要。
Prometheus监控
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
labels:
- "prometheus.io/scrape=true"
- "prometheus.io/port=3000"
- "prometheus.io/path=/metrics"
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- /var/run/docker.sock:/var/run/docker.sock:ro
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/etc/prometheus/console_libraries'
- '--web.console.templates=/etc/prometheus/consoles'
grafana:
image: grafana/grafana:latest
ports:
- "3001:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana-data:/var/lib/grafana
volumes:
grafana-data:ELK日志收集
version: '3.8'
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.5.0
environment:
- discovery.type=single-node
- xpack.security.enabled=false
ports:
- "9200:9200"
volumes:
- es-data:/usr/share/elasticsearch/data
logstash:
image: docker.elastic.co/logstash/logstash:8.5.0
volumes:
- ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf
depends_on:
- elasticsearch
kibana:
image: docker.elastic.co/kibana/kibana:8.5.0
ports:
- "5601:5601"
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
depends_on:
- elasticsearch
app:
build: .
logging:
driver: "gelf"
options:
gelf-address: "udp://localhost:12201"
tag: "myapp"
volumes:
es-data:使用Fluentd收集日志
version: '3.8'
services:
fluentd:
image: fluent/fluentd:v1.14-1
ports:
- "24224:24224"
- "24224:24224/udp"
volumes:
- ./fluentd.conf:/fluentd/etc/fluent.conf
environment:
- FLUENTD_CONF=fluent.conf
app:
build: .
logging:
driver: "fluentd"
options:
fluentd-address: localhost:24224
tag: docker.myappDocker性能调优
容器资源限制
合理设置资源限制,避免容器消耗过多资源:
# 内存限制
docker run -d --memory=1g --memory-swap=2g nginx:latest
# CPU限制
docker run -d --cpus=1.5 nginx:latest
docker run -d --cpu-shares=512 nginx:latest
# 磁盘IO限制
docker run -d --device-read-bps /dev/sda:1mb nginx:latest
docker run -d --device-write-bps /dev/sda:1mb nginx:latest优化Docker daemon
编辑/etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"storage-driver": "overlay2",
"storage-opts": [
"overlay2.override_kernel_check=true"
],
"default-ulimits": {
"nofile": {
"name": "nofile",
"hard": 65536,
"soft": 65536
}
},
"max-concurrent-downloads": 10,
"max-concurrent-uploads": 5
}容器启动优化
# 使用多阶段构建减少镜像大小
FROM node:16-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
FROM node:16-alpine
RUN apk --no-cache add dumb-init
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
USER node
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "server.js"]使用dumb-init作为PID 1进程,处理僵尸进程和信号转发。
故障排查和调试技巧
容器无法启动
# 查看容器日志
docker logs container_name
docker logs --details container_name
# 查看容器事件
docker events --filter container=container_name
# 进入容器调试(如果容器还在运行)
docker exec -it container_name /bin/bash
# 如果容器已经退出,用同样的镜像启动一个临时容器
docker run -it --rm --entrypoint /bin/bash image_name网络问题排查
# 查看容器网络配置
docker inspect container_name | jq '.[0].NetworkSettings'
# 测试网络连通性
docker exec container_name ping google.com
docker exec container_name nslookup google.com
docker exec container_name telnet another_container 3306
# 查看端口监听情况
docker exec container_name netstat -tulpn
docker exec container_name ss -tulpn性能问题排查
# 查看容器资源使用
docker stats container_name
# 查看容器进程
docker exec container_name ps aux
docker exec container_name top
# 查看系统调用
docker exec container_name strace -p 1
# 查看文件系统使用
docker exec container_name df -h
docker exec container_name du -sh /*使用dive分析镜像
dive是一个很好用的镜像分析工具:
# 安装dive
wget https://github.com/wagoodman/dive/releases/download/v0.10.0/dive_0.10.0_linux_amd64.deb
sudo apt install ./dive_0.10.0_linux_amd64.deb
# 分析镜像
dive myapp:latestdive可以显示镜像的每一层,帮助优化镜像大小。
总结
Docker确实是个好东西,能解决很多实际问题。从开发环境的统一,到生产部署的简化,再到微服务架构的实现,都离不开容器技术。
这篇文章涵盖了Docker的方方面面,从基础概念到高级应用,从网络配置到安全实践。特别是网络部分,我花了很多篇幅来讲解,因为这确实是很多人觉得困难的地方。
网络是Docker中比较复杂的部分,但一旦理解了原理,用起来就得心应手了。记住几个要点:
- 默认bridge网络适合简单场景
- 自定义网络支持服务发现,更适合多容器应用
- 生产环境要考虑网络安全和隔离
- 跨主机网络需要用overlay或第三方插件
不过也要理性看待,Docker不是银弹,不能解决所有问题。有些场景下,传统的部署方式可能更简单直接。关键是要根据实际需求选择合适的技术。
我自己用Docker这几年,最大的感受就是它让部署变得可预测了。以前部署应用总是提心吊胆,生怕环境不一致出问题。现在有了Docker,本地测试通过的应用,到生产环境基本不会有环境问题。
当然,Docker的学习曲线还是有的,特别是网络和存储这块。但一旦掌握了,效率提升还是很明显的。建议大家从简单的应用开始,慢慢积累经验。
最后,技术是为业务服务的,不要为了用Docker而用Docker。如果现有的部署方式已经很成熟,没必要强行迁移。但如果遇到了环境不一致、部署复杂等问题,Docker确实是个不错的选择。
希望这篇文章对大家有帮助,如果觉得有用的话,别忘了点赞转发。有问题也欢迎留言讨论,大家一起学习进步。
关注@运维躬行录,我会持续分享更多实用的运维技术和经验,让我们一起在运维的路上走得更远!
公众号:运维躬行录
个人博客:躬行笔记