Docker容器化部署实战
Docker作为容器化技术的领导者,已经成为现代软件开发和部署的标准工具。它通过容器化的方式解决了”在我的机器上能运行”的经典问题,实现了开发、测试和生产环境的一致性。
在本文中,我将从Docker的基础概念出发,深入讲解容器化部署的核心技术和实践方法,帮助你构建高效、可靠的应用部署架构。
1. Docker基础概念
1.1 什么是Docker
Docker是一个开源的容器化平台,它将应用程序及其依赖打包在一个轻量级的容器中。容器是轻量级、可移植、自包含的运行环境,可以在任何支持Docker的机器上运行。
1.2 Docker核心概念
- 镜像(Image):只读的模板,用于创建容器
- 容器(Container):镜像的运行实例
- Dockerfile:用于构建镜像的文本文件
- Docker Hub:公共镜像仓库
- Docker Compose:用于定义和运行多容器Docker应用程序的工具
1.3 Docker与传统虚拟化的区别
| 特性 | Docker容器 | 传统虚拟机 |
|---|
| 启动时间 | 秒级 | 分钟级 |
| 资源占用 | MB级别 | GB级别 |
| 隔离性 | 进程级 | 完全隔离 |
| 性能 | 接近原生 | 有损耗 |
| 可移植性 | 极好 | 一般 |
2. Docker安装与配置
2.1 Docker Desktop安装
brew install docker
curl -fsSL https://get.docker.com -o get-docker.sh sh get-docker.sh
|
2.2 Docker配置
sudo usermod -aG docker $USER
docker --version docker run hello-world
sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-'EOF' { "registry-mirrors": ["https://mirror.ccs.tencentyun.com"] } EOF sudo systemctl restart docker
|
3. Docker镜像管理
3.1 Dockerfile编写
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
FROM node:18-alpine AS runtime
RUN apk add --no-cache dumb-init
WORKDIR /app
RUN addgroup -g 1001 -S nodejs && \ adduser -S nodeuser -u 1001
COPY --from=builder /app/dist ./dist COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/package*.json ./
RUN chown -R nodeuser:nodejs /app && \ chmod -R 755 /app
USER nodeuser
EXPOSE 3000
ENTRYPOINT ["dumb-init", "--"] CMD ["node", "dist/server.js"]
|
3.2 构建镜像
docker build -t my-app:latest .
docker images
docker tag my-app:latest my-app:v1.0.0
docker push my-app:v1.0.0
docker rmi my-app:latest
|
3.3 镜像优化
FROM node:18-alpine AS deps WORKDIR /app COPY package*.json ./ RUN npm ci
FROM node:18-alpine AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . RUN npm run build
FROM node:18-alpine AS runner WORKDIR /app ENV NODE_ENV production COPY --from=builder /app/dist ./dist COPY --from=deps /app/node_modules ./node_modules EXPOSE 3000 CMD ["node", "dist/server.js"]
node_modules npm-debug.log *.log .git .nyc_output coverage .DS_Store
COPY package*.json ./ RUN npm ci COPY . .
|
4. Docker容器管理
4.1 容器生命周期管理
docker run -d --name my-app -p 3000:3000 my-app:latest
docker ps
docker stop my-app
docker start my-app
docker restart my-app
docker rm my-app
docker rm -f my-app
|
4.2 容器网络管理
docker network create my-network
docker run -d --name my-app --network my-network -p 3000:3000 my-app:latest
docker network ls
docker network inspect my-network
docker network connect my-network another-container
docker network disconnect my-network another-container
|
4.3 数据卷管理
docker volume create my-data
docker run -d --name my-app -v my-data:/app/data my-app:latest
docker volume ls
docker volume inspect my-data
docker volume rm my-data
|
5. Docker Compose多容器编排
5.1 Docker Compose文件
version: '3.8'
services: frontend: build: context: ./frontend dockerfile: Dockerfile ports: - "80:80" environment: - VITE_API_URL=http://backend:3000 depends_on: - backend networks: - app-network
backend: build: context: ./backend dockerfile: Dockerfile ports: - "3000:3000" environment: - NODE_ENV=production - DATABASE_URL=postgresql://user:password@db:5432/mydb - REDIS_URL=redis://redis:6379 depends_on: - db - redis volumes: - ./logs:/app/logs networks: - app-network
db: image: postgres:14-alpine environment: - POSTGRES_DB=mydb - POSTGRES_USER=user - POSTGRES_PASSWORD=password volumes: - db-data:/var/lib/postgresql/data - ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql networks: - app-network
redis: image: redis:7-alpine ports: - "6379:6379" volumes: - redis-data:/data networks: - app-network
nginx: image: nginx:alpine ports: - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf - ./ssl:/etc/nginx/ssl depends_on: - frontend - backend networks: - app-network
prometheus: image: prom/prometheus:latest ports: - "9090:9090" volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml - prometheus-data:/prometheus networks: - app-network
fluentd: build: context: ./logging/fluentd volumes: - ./logs:/fluentd/log - ./logging/fluentd/conf:/fluentd/etc networks: - app-network
volumes: db-data: driver: local redis-data: driver: local prometheus-data: driver: local
networks: app-network: driver: bridge ipam: config: - subnet: 172.20.0.0/16
|
5.2 Docker Compose操作
docker-compose up -d
docker-compose ps
docker-compose logs -f backend
docker-compose down
docker-compose down --volumes
docker-compose up -d --build
docker-compose up -d --scale backend=3
docker-compose exec backend sh
|
6. Docker安全配置
6.1 Dockerfile安全加固
FROM node:18-alpine AS base
RUN addgroup --system app && adduser --system --group app
WORKDIR /app RUN chown app:app /app
USER app
RUN apk add --no-cache --no-install-recommends dumb-init
COPY --chown=app:app package*.json ./ RUN npm ci --only=production && npm cache clean --force
COPY --chown=app:app . .
RUN chown -R app:app /app && chmod -R 755 /app
|
6.2 运行时安全
docker run -d --read-only --name my-app my-app:latest
docker run -d --name my-app \ --memory=512m \ --cpus=1.0 \ --pids-limit=100 \ my-app:latest
docker run -d --name my-app \ --security-opt=no-new-privileges \ --cap-drop=ALL \ my-app:latest
docker run -d --name my-app \ --network none \ my-app:latest
|
6.3 扫描漏洞
npm install -g docker-trivy
trivy image my-app:latest
tridy image --all
trivy docker-compose --file docker-compose.yml
|
7. Docker监控与日志
7.1 Prometheus监控配置
global: scrape_interval: 15s evaluation_interval: 15s
rule_files: - "rules/*.yml"
scrape_configs: - job_name: 'docker-containers' static_configs: - targets: ['cadvisor:8080'] scrape_interval: 5s
- job_name: 'my-app' static_configs: - targets: ['backend:3000'] scrape_interval: 10s
- job_name: 'nginx' static_configs: - targets: ['nginx:80'] scrape_interval: 10s
|
7.2 日志管理
ENV LOG_LEVEL=info ENV LOG_FORMAT=json
FROM fluent/fluentd:v1.16-1
RUN gem install fluent-plugin-prometheus
COPY fluent.conf /fluentd/etc/fluent.conf
|
# fluent.conf <source> @type tail path /var/log/containers/*.log pos_file /fluentd/log/pos tag docker.* format json time_format %Y-%m-%dT%H:%M:%S.%NZ </source>
<match docker.**> @type prometheus <metric> name docker_container_cpu_usage type counter <labels> container_id ${record["container_id"]} image ${record["image"]} </labels> </metric> </match>
|
7.3 Grafana可视化
version: '3.8'
services: prometheus: image: prom/prometheus:latest ports: - "9090:9090" volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml - prometheus-data:/prometheus
grafana: image: grafana/grafana:latest ports: - "3001:3000" environment: - GF_SECURITY_ADMIN_PASSWORD=admin volumes: - grafana-data:/var/lib/grafana
cadvisor: image: gcr.io/cadvisor/cadvisor:latest ports: - "8080:8080" volumes: - /:/rootfs:ro - /var/run:/var/run:ro - /sys:/sys:ro - /var/lib/docker:/var/lib/docker:ro devices: - /dev/disk
volumes: prometheus-data: grafana-data:
|
8. Kubernetes集成
8.1 Kubernetes部署文件
apiVersion: apps/v1 kind: Deployment metadata: name: my-app-deployment labels: app: my-app spec: replicas: 3 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: containers: - name: my-app image: my-app:latest ports: - containerPort: 3000 env: - name: NODE_ENV value: "production" - name: DATABASE_URL valueFrom: secretKeyRef: name: db-secret key: url resources: requests: memory: "256Mi" cpu: "250m" limits: memory: "512Mi" cpu: "500m" livenessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /ready port: 3000 initialDelaySeconds: 5 periodSeconds: 5 volumeMounts: - name: app-data mountPath: /app/data volumes: - name: app-data persistentVolumeClaim: claimName: my-app-pvc
|
apiVersion: v1 kind: Service metadata: name: my-app-service spec: selector: app: my-app ports: - protocol: TCP port: 80 targetPort: 3000 type: LoadBalancer
|
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: my-app-ingress annotations: nginx.ingress.kubernetes.io/rewrite-target: / cert-manager.io/cluster-issuer: letsencrypt-prod spec: tls: - hosts: - app.example.com secretName: app-tls rules: - host: app.example.com http: paths: - path: / pathType: Prefix backend: service: name: my-app-service port: number: 80
|
8.2 Helm Chart部署
apiVersion: v2 name: my-app description: A Helm chart for My App version: 1.0.0 appVersion: 1.0.0
|
replicaCount: 3 image: repository: my-app tag: latest pullPolicy: IfNotPresent
service: type: LoadBalancer port: 80
ingress: enabled: true annotations: nginx.ingress.kubernetes.io/rewrite-target: / hosts: - host: app.example.com paths: - path: /
resources: limits: cpu: 500m memory: 512Mi requests: cpu: 250m memory: 256Mi
autoscaling: enabled: true minReplicas: 3 maxReplicas: 10 targetCPUUtilizationPercentage: 70 targetMemoryUtilizationPercentage: 80
persistence: enabled: true size: 10Gi accessMode: ReadWriteOnce
|
apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "my-app.fullname" . }} labels: {{- include "my-app.labels" . | nindent 4 }} spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: {{- include "my-app.selectorLabels" . | nindent 6 }} template: metadata: {{- with .Values.podAnnotations }} annotations: {{- toYaml . | nindent 8 }} {{- end }} labels: {{- include "my-app.labels" . | nindent 8 }} spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - name: http containerPort: 80 protocol: TCP livenessProbe: httpGet: path: /health port: http readinessProbe: httpGet: path: /ready port: http resources: {{- toYaml .Values.resources | nindent 12 }} env: - name: NODE_ENV value: "production" {{- if .Values.ingress.enabled }} - name: APP_URL value: "https://{{ .Values.ingress.hosts[0].host }}" {{- end }} volumeMounts: - name: data mountPath: /app/data volumes: - name: data persistentVolumeClaim: claimName: {{ include "my-app.fullname" . }}-pvc
|
8.3 Kubernetes部署脚本
#!/bin/bash
set -e
export KUBECONFIG=kubeconfig
kubectl create namespace my-app --dry-run=client -o yaml | kubectl apply -f -
kubectl create secret generic db-secret \ --from-literal=DATABASE_URL="postgresql://user:password@db:5432/mydb" \ --namespace=my-app
helm install my-app ./my-app-chart \ --namespace=my-app \ --create-namespace \ --wait
kubectl get pods --namespace=my-app kubectl get services --namespace=my-app
INGRESS_IP=$(kubectl get ingress my-app-ingress -n my-app -o jsonpath='{.status.loadBalancer.ingress[0].ip}') echo "Application available at: https://$INGRESS_IP"
|
9. CI/CD流水线集成
9.1 GitHub Actions配置
name: Deploy to Kubernetes
on: push: branches: [ main ] pull_request: branches: [ main ]
env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }}
jobs: build: runs-on: ubuntu-latest permissions: contents: read packages: write
steps: - name: Checkout repository uses: actions/checkout@v3
- name: Set up Docker Buildx uses: docker/setup-buildx-action@v2
- name: Log in to Container Registry if: github.event_name != 'pull_request' uses: docker/login-action@v2 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata id: meta uses: docker/metadata-action@v4 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | type=ref,event=branch type=ref,event=pr type=sha,prefix={{branch}}- type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image uses: docker/build-push-action@v4 with: context: . push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }}
deploy: needs: build runs-on: ubuntu-latest if: github.ref == 'refs/heads/main'
steps: - name: Checkout repository uses: actions/checkout@v3
- name: Configure Kubernetes uses: azure/k8s-set-context@v1 with: kubeconfig: ${{ secrets.KUBECONFIG }}
- name: Deploy to Kubernetes run: | kubectl apply -f k8s/
|
9.2 GitLab CI/CD配置
stages: - build - test - deploy
variables: DOCKER_DRIVER: overlay2 DOCKER_TLS_CERTDIR: "/certs"
services: - docker:dind
build: stage: build image: docker:latest variables: IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA before_script: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY script: - docker build -t $IMAGE_TAG . - docker push $IMAGE_TAG
test: stage: test image: node:18 script: - npm install - npm test - npm run build - npm run test:e2e
deploy-staging: stage: deploy environment: name: staging dependencies: - build script: - kubectl config use-context staging-cluster - kubectl set image deployment/my-app my-app=$IMAGE_TAG - kubectl rollout status deployment/my-app only: - main
deploy-production: stage: deploy environment: name: production url: https://app.example.com dependencies: - build script: - kubectl config use-context production-cluster - kubectl set image deployment/my-app my-app=$IMAGE_TAG - kubectl rollout status deployment/my-app only: - main when: manual
|
10. 故障排查与优化
10.1 常见问题排查
docker logs my-app
docker inspect my-app
docker exec -it my-app sh
docker info
docker system prune
docker network inspect my-network
docker stats my-app
|
10.2 性能优化
FROM node:18-alpine AS deps WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci
FROM node:18-alpine AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . RUN npm run build
FROM node:18-alpine AS runtime WORKDIR /app COPY --from=builder /app/dist ./dist COPY --from=deps /app/node_modules ./node_modules EXPOSE 3000 CMD ["node", "dist/server.js"]
|
docker build --build-arg BUILDKIT_INLINE_CACHE=1 .
docker build --target runtime -t my-app:runtime .
docker run --memory=512m --cpus=1.0 my-app:latest
|
10.3 监控工具
docker run \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /proc:/host/proc:ro \ -v /sys:/host/sys:ro \ -v /etc/os-release:/etc/os-release:ro \ -p 8080:8080 \ gcr.io/cadvisor/cadvisor:latest
docker run -d -p 9090:9090 \ -v /path/to/prometheus.yml:/etc/prometheus/prometheus.yml \ prom/prometheus
docker run -d -p 3000:3000 \ -v /path/to/grafana/provisioning:/etc/grafana/provisioning \ grafana/grafana:latest
|
11. 实际应用案例
11.1 微服务架构部署
version: '3.8'
services: gateway: build: context: ./gateway dockerfile: Dockerfile ports: - "8080:8080" environment: - SERVICE_DISCOVERY=http://registry:8761 depends_on: - registry networks: - microservices
registry: image: springcloud/eureka:latest ports: - "8761:8761" networks: - microservices
user-service: build: context: ./user-service dockerfile: Dockerfile ports: - "8081:8080" environment: - SPRING_PROFILES_ACTIVE=docker - DATABASE_URL=jdbc:mysql://db:3306/user_db depends_on: - db - registry networks: - microservices
order-service: build: context: ./order-service dockerfile: Dockerfile ports: - "8082:8080" environment: - SPRING_PROFILES_ACTIVE=docker - DATABASE_URL=jdbc:mysql://db:3306/order_db - USER_SERVICE_URL=http://user-service:8080 depends_on: - db - user-service - registry networks: - microservices
product-service: build: context: ./product-service dockerfile: Dockerfile ports: - "8083:8080" environment: - SPRING_PROFILES_ACTIVE=docker - DATABASE_URL=jdbc:mysql://db:3306/product_db depends_on: - db - registry networks: - microservices
db: image: mysql:8.0 ports: - "3306:3306" environment: - MYSQL_ROOT_PASSWORD=rootpassword - MYSQL_DATABASE=mydb volumes: - mysql-data:/var/lib/mysql networks: - microservices
redis: image: redis:7-alpine ports: - "6379:6379" volumes: - redis-data:/data networks: - microservices
rabbitmq: image: rabbitmq:3-management ports: - "5672:5672" - "15672:15672" environment: - RABBITMQ_DEFAULT_USER=admin - RABBITMQ_DEFAULT_PASS=admin123 volumes: - rabbitmq-data:/var/lib/rabbitmq networks: - microservices
volumes: mysql-data: redis-data: rabbitmq-data:
networks: microservices: driver: bridge
|
11.2 容器化迁移案例
#!/bin/bash
echo "评估现有应用..." docker run --rm -v /var/lib/docker:/var/lib/docker alpine:3.12 \ find /var/lib/docker/volumes -name "*mysql*" -o -name "*redis*"
echo "创建Dockerfile..." cat << 'EOF' > Dockerfile FROM nginx:alpine COPY html /usr/share/nginx/html COPY nginx.conf /etc/nginx/nginx.conf EXPOSE 80 EOF
echo "构建Docker镜像..." docker build -t legacy-app:latest .
echo "启动新容器..." docker run -d --name legacy-app-new \ --restart unless-stopped \ -p 80:80 \ legacy-app:latest
echo "验证迁移..." sleep 10 curl -I http://localhost:80 | grep "200 OK"
echo "迁移数据..." docker run --rm -v legacy-data:/data \ -v mysql-backup:/backup \ busybox ash -c "cp -r /data/* /backup/"
echo "创建回滚脚本..." cat << 'EOF' > rollback.sh
docker stop legacy-app-new docker rm legacy-app-new docker run -d --name legacy-app-old \ --restart unless-stopped \ -p 80:80 \ legacy-app:v1.0.0 EOF chmod +x rollback.sh
echo "迁移完成!"
|
12. 总结与展望
12.1 Docker最佳实践
- 使用多阶段构建:减小镜像大小,提高安全性
- 遵循最小权限原则:使用非root用户运行容器
- 使用.dockerignore:减少构建上下文大小
- 定期更新基础镜像:保持系统包的及时更新
- 使用版本标签:避免使用latest标签
- 限制容器资源:防止资源滥用
- 使用健康检查:确保容器健康状态
- 日志管理:使用结构化日志,便于分析
12.2 未来发展趋势
- Kubernetes成为标准:容器编排的统一平台
- Serverless架构:无服务器容器化
- 边缘计算:在边缘节点运行容器
- 安全加固:容器安全性的持续改进
- AI/ML集成:智能化容器管理
- GitOps工作流:声明式基础设施管理
12.3 学习建议
- 掌握基础知识:深入学习Docker核心概念
- 实践项目:通过实际项目积累经验
- 关注社区:了解最新的技术动态
- 学习Kubernetes:掌握容器编排技术
- 参与开源项目:贡献代码,提升技能
13. 结语
Docker容器化技术已经深刻改变了软件开发和部署的方式。通过本文的学习,你应该已经掌握了Docker的核心技术和实践方法,能够将应用容器化并部署到生产环境。
记住,容器化不仅仅是一个技术问题,更是一个思维方式。它需要我们重新思考应用的设计、部署和运维方式。持续学习和实践,你将成为一名优秀的DevOps工程师。
希望本文能够帮助你更好地理解和使用Docker。如果你有任何问题或建议,欢迎在评论区交流分享!
本文由笔者根据实际项目经验总结,如有疏漏之处,敬请指正。