Docker 与 Kubernetes 部署实战:云原生应用交付 Docker 与 Kubernetes 部署实战云原生应用交付别叫我大神叫我 Alex 就好。容器化不是目的而是手段。Kubernetes 让应用部署变得标准化、自动化。一、Docker 最佳实践1.1 多阶段构建# Dockerfile - 多阶段构建示例 # 构建阶段 FROM maven:3.8-openjdk-17 AS builder WORKDIR /app COPY pom.xml . COPY src ./src RUN mvn clean package -DskipTests # 运行阶段 FROM eclipse-temurin:17-jre-alpine WORKDIR /app # 创建非 root 用户 RUN addgroup -S appgroup adduser -S appuser -G appgroup # 从构建阶段复制 jar COPY --frombuilder /app/target/*.jar app.jar # 设置权限 RUN chown -R appuser:appgroup /app USER appuser # 健康检查 HEALTHCHECK --interval30s --timeout3s --start-period60s --retries3 \ CMD wget --quiet --tries1 --spider http://localhost:8080/actuator/health || exit 1 EXPOSE 8080 # JVM 参数优化 ENV JAVA_OPTS-XX:UseContainerSupport -XX:MaxRAMPercentage75.0 -XX:InitialRAMPercentage50.0 ENTRYPOINT [sh, -c, java $JAVA_OPTS -jar app.jar]1.2 Docker Compose 配置# docker-compose.yml version: 3.8 services: app: build: context: . dockerfile: Dockerfile image: myapp:latest container_name: myapp ports: - 8080:8080 environment: - SPRING_PROFILES_ACTIVEdocker - SPRING_DATASOURCE_URLjdbc:postgresql://postgres:5432/mydb - SPRING_REDIS_HOSTredis - SPRING_KAFKA_BOOTSTRAP_SERVERSkafka:9092 depends_on: - postgres - redis - kafka networks: - app-network restart: unless-stopped deploy: resources: limits: cpus: 1.0 memory: 1G reservations: cpus: 0.5 memory: 512M postgres: image: postgres:15-alpine container_name: postgres environment: - POSTGRES_DBmydb - POSTGRES_USERappuser - POSTGRES_PASSWORDsecretpassword volumes: - postgres_data:/var/lib/postgresql/data - ./init.sql:/docker-entrypoint-initdb.d/init.sql ports: - 5432:5432 networks: - app-network healthcheck: test: [CMD-SHELL, pg_isready -U appuser -d mydb] interval: 10s timeout: 5s retries: 5 redis: image: redis:7-alpine container_name: redis command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru volumes: - redis_data:/data ports: - 6379:6379 networks: - app-network kafka: image: confluentinc/cp-kafka:7.5.0 container_name: kafka environment: KAFKA_BROKER_ID: 1 KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092 KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 depends_on: - zookeeper networks: - app-network zookeeper: image: confluentinc/cp-zookeeper:7.5.0 container_name: zookeeper environment: ZOOKEEPER_CLIENT_PORT: 2181 ZOOKEEPER_TICK_TIME: 2000 networks: - app-network volumes: postgres_data: redis_data: networks: app-network: driver: bridge二、Kubernetes 基础资源2.1 Deployment 配置# deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: myapp labels: app: myapp version: v1 spec: replicas: 3 strategy: type: RollingUpdate rollingUpdate: maxSurge: 25% maxUnavailable: 0 selector: matchLabels: app: myapp template: metadata: labels: app: myapp version: v1 spec: serviceAccountName: myapp-sa securityContext: runAsNonRoot: true runAsUser: 1000 fsGroup: 1000 containers: - name: myapp image: myregistry/myapp:v1.0.0 imagePullPolicy: Always ports: - name: http containerPort: 8080 protocol: TCP env: - name: SPRING_PROFILES_ACTIVE value: kubernetes - name: JAVA_OPTS value: -XX:UseContainerSupport -XX:MaxRAMPercentage75.0 - name: DB_PASSWORD valueFrom: secretKeyRef: name: myapp-secrets key: db-password resources: requests: memory: 512Mi cpu: 500m limits: memory: 1Gi cpu: 1000m livenessProbe: httpGet: path: /actuator/health/liveness port: 8080 initialDelaySeconds: 60 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 readinessProbe: httpGet: path: /actuator/health/readiness port: 8080 initialDelaySeconds: 30 periodSeconds: 5 timeoutSeconds: 3 failureThreshold: 3 volumeMounts: - name: config-volume mountPath: /app/config - name: tmp-volume mountPath: /tmp volumes: - name: config-volume configMap: name: myapp-config - name: tmp-volume emptyDir: {} affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - myapp topologyKey: kubernetes.io/hostname2.2 Service 和 Ingress# service.yaml apiVersion: v1 kind: Service metadata: name: myapp-service labels: app: myapp spec: type: ClusterIP ports: - port: 80 targetPort: 8080 protocol: TCP name: http selector: app: myapp --- # ingress.yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: myapp-ingress annotations: nginx.ingress.kubernetes.io/ssl-redirect: true nginx.ingress.kubernetes.io/proxy-body-size: 10m nginx.ingress.kubernetes.io/rate-limit: 100 cert-manager.io/cluster-issuer: letsencrypt-prod spec: ingressClassName: nginx tls: - hosts: - api.example.com secretName: myapp-tls rules: - host: api.example.com http: paths: - path: / pathType: Prefix backend: service: name: myapp-service port: number: 80三、ConfigMap 和 Secret# configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: myapp-config data: application.yml: | server: port: 8080 spring: datasource: url: jdbc:postgresql://postgres-service:5432/mydb username: appuser hikari: maximum-pool-size: 20 minimum-idle: 5 redis: host: redis-service port: 6379 timeout: 2000ms lettuce: pool: max-active: 8 max-idle: 8 kafka: bootstrap-servers: kafka-service:9092 producer: retries: 3 acks: all logging: level: root: INFO com.example: DEBUG --- # secret.yaml apiVersion: v1 kind: Secret metadata: name: myapp-secrets type: Opaque stringData: db-password: your-secure-password jwt-secret: your-jwt-secret-key api-key: your-api-key四、HPA 和 VPA# hpa.yaml apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: myapp-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: myapp minReplicas: 3 maxReplicas: 20 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 - type: Resource resource: name: memory target: type: Utilization averageUtilization: 80 - type: Pods pods: metric: name: http_requests_per_second target: type: AverageValue averageValue: 1000 behavior: scaleUp: stabilizationWindowSeconds: 60 policies: - type: Percent value: 100 periodSeconds: 15 scaleDown: stabilizationWindowSeconds: 300 policies: - type: Percent value: 10 periodSeconds: 60 --- # vpa.yaml apiVersion: autoscaling.k8s.io/v1 kind: VerticalPodAutoscaler metadata: name: myapp-vpa spec: targetRef: apiVersion: apps/v1 kind: Deployment name: myapp updatePolicy: updateMode: Auto resourcePolicy: containerPolicies: - containerName: myapp minAllowed: cpu: 100m memory: 256Mi maxAllowed: cpu: 2000m memory: 2Gi controlledResources: [cpu, memory]五、持久化存储# pvc.yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: postgres-pvc spec: accessModes: - ReadWriteOnce storageClassName: fast-ssd resources: requests: storage: 100Gi --- # statefulset.yaml apiVersion: apps/v1 kind: StatefulSet metadata: name: postgres spec: serviceName: postgres-headless replicas: 1 selector: matchLabels: app: postgres template: metadata: labels: app: postgres spec: containers: - name: postgres image: postgres:15-alpine ports: - containerPort: 5432 env: - name: POSTGRES_DB value: mydb - name: POSTGRES_USER value: appuser - name: POSTGRES_PASSWORD valueFrom: secretKeyRef: name: postgres-secrets key: password volumeMounts: - name: postgres-storage mountPath: /var/lib/postgresql/data resources: requests: memory: 1Gi cpu: 500m limits: memory: 2Gi cpu: 1000m volumeClaimTemplates: - metadata: name: postgres-storage spec: accessModes: [ReadWriteOnce] storageClassName: fast-ssd resources: requests: storage: 100Gi六、CI/CD 集成# .github/workflows/deploy.yml name: Build and Deploy 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: - uses: actions/checkoutv4 - name: Set up JDK 17 uses: actions/setup-javav4 with: java-version: 17 distribution: temurin - name: Build with Maven run: ./mvnw clean package -DskipTests - name: Run tests run: ./mvnw test - name: Set up Docker Buildx uses: docker/setup-buildx-actionv3 - name: Log in to Container Registry uses: docker/login-actionv3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata id: meta uses: docker/metadata-actionv5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | typeref,eventbranch typeref,eventpr typesha,prefix{{branch}}- typeraw,valuelatest,enable{{is_default_branch}} - name: Build and push Docker image uses: docker/build-push-actionv5 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: typegha cache-to: typegha,modemax deploy: needs: build runs-on: ubuntu-latest if: github.ref refs/heads/main steps: - uses: actions/checkoutv4 - name: Configure kubectl uses: azure/setup-kubectlv3 - name: Set up Kustomize run: | curl -s https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh | bash sudo mv kustomize /usr/local/bin/ - name: Configure AWS credentials uses: aws-actions/configure-aws-credentialsv4 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: us-west-2 - name: Update kubeconfig run: aws eks update-kubeconfig --name my-cluster - name: Deploy to Kubernetes run: | cd k8s/overlays/production kustomize edit set image myapp${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${GITHUB_SHA::7} kustomize build . | kubectl apply -f - kubectl rollout status deployment/myapp七、监控与日志# servicemonitor.yaml apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: myapp-metrics labels: release: prometheus spec: selector: matchLabels: app: myapp endpoints: - port: http path: /actuator/prometheus interval: 15s --- # podmonitor.yaml apiVersion: monitoring.coreos.com/v1 kind: PodMonitor metadata: name: myapp-jvm-metrics spec: selector: matchLabels: app: myapp podMetricsEndpoints: - port: http path: /actuator/prometheus interval: 15s --- # networkpolicy.yaml apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: myapp-network-policy spec: podSelector: matchLabels: app: myapp policyTypes: - Ingress - Egress ingress: - from: - namespaceSelector: matchLabels: name: ingress-nginx ports: - protocol: TCP port: 8080 egress: - to: - podSelector: matchLabels: app: postgres ports: - protocol: TCP port: 5432 - to: - podSelector: matchLabels: app: redis ports: - protocol: TCP port: 6379八、总结容器化和 Kubernetes 部署的核心要点镜像优化多阶段构建、最小化镜像资源配置合理的 requests 和 limits健康检查liveness 和 readiness probe弹性伸缩HPA 和 VPA 配置安全加固非 root 用户、NetworkPolicy这其实可以更优雅一点。云原生部署不仅是技术更是一种工程文化。参考资源Docker DocumentationKubernetes DocumentationKubernetes Best Practices