Docker+K8s 部署微服务:从搭建到运维的全流程指南(Java 老鸟实战版)
作为一名摸爬滚打八年的 Java 开发,从最初的单体应用 WAR 包扔 Tomcat,到后来的微服务集群部署,踩过的坑能绕公司机房三圈。其中最让人头疼的就是「环境一致性」和「运维复杂度」------ 开发环境跑的飞起,测试环境各种报错,生产环境突然雪崩,排查半天发现是配置不一致、端口冲突、依赖缺失...
直到 Docker+K8s 组合横空出世,才算彻底解决了这些痛点。这篇文章就从 Java 开发的视角,带大家走一遍「微服务容器化 + K8s 编排」的全流程,从环境搭建到生产运维,每一步都附实战代码和避坑指南,保证看完就能落地。
一、为什么选择 Docker+K8s?(八年经验的选型逻辑)
在聊技术细节前,先说说我为什么推荐这个组合。作为 Java 开发,我们可能接触过 Jenkins+Ansible、Docker Compose、甚至是云厂商的容器服务,但最终选择 Docker+K8s,核心原因有 3 点:
- 环境一致性:一次构建,到处运行以前部署微服务,要在每个服务器装 JDK、配置环境变量、解决依赖冲突,甚至还要注意 JVM 参数和服务器内核的兼容性。用 Docker 打包后,Java 应用、JDK、依赖库、配置文件全被封装在镜像里,开发、测试、生产环境完全一致,再也不用喊 "我这能跑啊"。
- 弹性伸缩:应对流量峰值不慌微服务的核心优势是可扩展,但手动扩容时,要先申请服务器、装环境、部署应用,等操作完流量可能已经过去了。K8s 能根据 CPU、内存使用率自动扩缩容,比如双 11 峰值时自动加 Pod,峰值过后自动缩容,省资源又省心。
- 运维自动化:解放双手,专注业务以前上线要手动停服务、传包、启动,还要担心回滚问题。K8s 支持滚动更新、灰度发布、一键回滚,配合 CI/CD 工具(比如 Jenkins、GitLab CI),可以实现代码提交后自动构建镜像、部署到 K8s,全程无需手动干预。
避坑提醒:很多中小团队会觉得 K8s 太重,其实现在 kubeadm 搭建集群已经很简单,而且 K8s 的核心功能(部署、扩缩容、服务发现)完全能满足微服务的需求,后续扩展也方便。如果团队规模特别小,也可以先从 Docker Compose 入手,但长远来看,K8s 是趋势。
二、环境准备(生产级配置,拒绝 "玩具集群")
2.1 服务器配置(最少 3 节点,生产推荐 4 核 8G 起)
| 节点角色 | 数量 | 配置要求 | 操作系统 |
|---|---|---|---|
| Master 节点 | 1 | 4 核 8G,50G 硬盘 | CentOS 7.9 / Ubuntu 20.04 |
| Worker 节点 | 2+ | 4 核 8G,100G 硬盘 | CentOS 7.9 / Ubuntu 20.04 |
| 镜像仓库节点 | 1 | 2 核 4G,100G 硬盘 | CentOS 7.9 / Ubuntu 20.04 |
说明:我这里用的是 CentOS 7.9,因为生产环境中 CentOS 的稳定性和兼容性更好。如果用 Ubuntu,命令略有差异,但核心步骤一致。
2.2 必备软件版本(兼容性优先,避免踩版本坑)
- Docker:20.10.x(LTS 版本,稳定为主,不要用最新版)
- K8s:1.24.x(1.24 是比较成熟的版本,后续版本改动较大,新手不建议追新)
- 镜像仓库:Harbor 2.5.x(企业级镜像仓库,支持权限管理、镜像扫描)
- JDK:11(微服务推荐 JDK 11,长期支持版本)
2.3 环境初始化(所有节点执行,关键步骤)
1. 关闭防火墙和 SELinux(生产环境可配置白名单,这里简化操作)
bash
# 关闭防火墙
systemctl stop firewalld
systemctl disable firewalld
# 关闭SELinux
setenforce 0
sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config
2. 关闭 swap 分区(K8s 要求,否则会影响性能和稳定性)
bash
swapoff -a
sed -i '/swap/s/^/#/' /etc/fstab
3. 配置内核参数(开启 IP 转发和桥接)
ini
cat > /etc/sysctl.d/k8s.conf << EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
EOF
# 生效配置
sysctl --system
4. 安装 Docker(所有节点)
bash
# 安装依赖
yum install -y yum-utils device-mapper-persistent-data lvm2
# 配置Docker镜像源(阿里云,速度快)
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
# 安装指定版本Docker
yum install -y docker-ce-20.10.24 docker-ce-cli-20.10.24 containerd.io
# 启动Docker并设置开机自启
systemctl start docker
systemctl enable docker
# 配置Docker镜像加速(阿里云,注册账号后获取专属加速地址)
mkdir -p /etc/docker
cat > /etc/docker/daemon.json << EOF
{
"registry-mirrors": ["https://xxxx.mirror.aliyuncs.com"],
"exec-opts": ["native.cgroupdriver=systemd"] # 关键:K8s推荐使用systemd驱动
}
EOF
# 重启Docker
systemctl daemon-reload
systemctl restart docker
避坑提醒:Docker 的 cgroup 驱动必须设置为 systemd,否则 K8s 初始化会报错。这是我当年踩过的第一个大坑,记忆犹新。
三、Docker 实战:微服务容器化
作为 Java 开发,我们的核心目标是把 Spring Boot 微服务打包成 Docker 镜像。这一步要注意镜像瘦身、分层构建,避免镜像过大导致部署缓慢。
3.1 编写 Dockerfile(Java 微服务优化版)
以一个标准的 Spring Boot 微服务为例,Dockerfile 放在项目根目录:
bash
# 第一阶段:构建应用(使用maven镜像编译打包)
FROM maven:3.8.8-openjdk-11 AS builder
# 设置工作目录
WORKDIR /app
# 复制pom.xml和src目录
COPY pom.xml .
COPY src ./src
# 编译打包(跳过测试,加快构建速度)
RUN mvn clean package -DskipTests
# 第二阶段:运行应用(使用轻量级openjdk镜像)
FROM openjdk:11-jre-slim
# 设置工作目录
WORKDIR /app
# 复制构建阶段的jar包到当前目录(改名为app.jar,简洁)
COPY --from=builder /app/target/*.jar app.jar
# 配置JVM参数(根据服务器配置调整,避免OOM)
ENV JAVA_OPTS="-Xms512m -Xmx512m -XX:+UseG1GC -XX:MaxGCPauseMillis=200"
# 暴露微服务端口(根据实际应用端口修改)
EXPOSE 8080
# 启动命令(推荐用exec格式,保证容器退出信号能传递给应用)
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
关键优化点:
- 分层构建:第一阶段用 maven 镜像编译,第二阶段用 jre-slim 镜像运行,镜像大小从几百 MB 缩减到 100MB 左右。
- JVM 参数优化:-Xms 和 - Xmx 设置为相同值,避免频繁扩容;使用 G1GC 垃圾收集器,适合微服务场景。
- 入口命令:用 exec 格式,确保
docker stop能正常终止应用,而不是强制杀死进程。
3.2 构建 Docker 镜像
bash
# 进入项目根目录
cd /path/to/your/project
# 构建镜像(格式:仓库地址/项目名/服务名:版本号)
docker build -t harbor.example.com/microservice/user-service:1.0.0 .
3.3 搭建 Harbor 镜像仓库(企业级必备)
Docker Hub 限速,而且私有镜像需要付费,所以生产环境一定要搭建自己的 Harbor 仓库。
1. 安装 Docker Compose(Harbor 依赖)
bash
curl -L "https://github.com/docker/compose/releases/download/v2.20.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
# 验证安装
docker-compose --version
2. 部署 Harbor
bash
# 下载Harbor安装包(版本2.5.3,稳定版)
wget https://github.com/goharbor/harbor/releases/download/v2.5.3/harbor-offline-installer-v2.5.3.tgz
# 解压
tar -zxvf harbor-offline-installer-v2.5.3.tgz -C /usr/local/
# 进入Harbor目录
cd /usr/local/harbor
# 复制配置文件
cp harbor.yml.tmpl harbor.yml
# 修改配置文件(关键配置)
vi harbor.yml
关键配置修改:
yaml
hostname: harbor.example.com # 改为你的服务器IP或域名
http:
port: 80
# https: # 生产环境建议配置HTTPS,这里简化用HTTP
# port: 443
# certificate: /path/to/cert
# private_key: /path/to/key
harbor_admin_password: Harbor12345 # 管理员密码,建议修改为复杂密码
data_volume: /data/harbor # 镜像存储目录,建议挂载大硬盘
3. 启动 Harbor
bash
# 执行安装脚本
./install.sh
# 启动Harbor(后续重启用)
docker-compose -f /usr/local/harbor/docker-compose.yml up -d
4. 推送镜像到 Harbor
bash
# 登录Harbor(输入用户名admin,密码Harbor12345)
docker login harbor.example.com
# 推送镜像
docker push harbor.example.com/microservice/user-service:1.0.0
# 退出登录(可选)
docker logout harbor.example.com
避坑提醒:如果 Harbor 用 HTTP 协议,需要在所有 K8s 节点的 Docker 配置中添加信任:在
/etc/docker/daemon.json中添加"insecure-registries": ["harbor.example.com"],然后重启 Docker。
四、K8s 集群搭建(kubeadm 实战版)
4.1 安装 K8s 组件(所有节点)
ini
# 配置K8s镜像源(阿里云)
cat > /etc/yum.repos.d/kubernetes.repo << EOF
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-$basearch/
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
exclude=kubelet kubeadm kubectl
EOF
# 安装指定版本K8s组件
yum install -y kubelet-1.24.10 kubeadm-1.24.10 kubectl-1.24.10 --disableexcludes=kubernetes
# 启动kubelet并设置开机自启
systemctl start kubelet
systemctl enable kubelet
4.2 初始化 Master 节点
bash
# 初始化Master(指定镜像源、Pod网段)
kubeadm init \
--image-repository registry.aliyuncs.com/google_containers \
--kubernetes-version v1.24.10 \
--pod-network-cidr=10.244.0.0/16 \
--service-cidr=10.96.0.0/12
# 配置kubectl(当前用户)
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
# 验证Master节点状态
kubectl get nodes
初始化成功后,会输出 Worker 节点加入集群的命令,类似:
sql
kubeadm join 192.168.1.100:6443 --token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
4.3 安装网络插件(Calico,必装)
K8s 集群需要网络插件才能让 Pod 之间通信,推荐用 Calico(稳定、性能好):
bash
kubectl apply -f https://docs.projectcalico.org/v3.24/manifests/calico.yaml
# 验证网络插件状态
kubectl get pods -n kube-system
4.4 Worker 节点加入集群
在所有 Worker 节点执行 Master 初始化时输出的kubeadm join命令。如果忘记了命令,可以在 Master 节点执行:
lua
kubeadm token create --print-join-command
4.5 验证集群状态
csharp
# 查看所有节点(状态为Ready表示正常)
kubectl get nodes
# 查看集群核心组件
kubectl get pods -n kube-system
避坑提醒:
- 如果 kubelet 启动失败,大概率是 Docker 的 cgroup 驱动配置错误,回去检查
daemon.json中的native.cgroupdriver=systemd。- Calico 安装后如果 Pod 一直处于 Pending 状态,可能是服务器内存不足(至少 4G),或者 Pod 网段与服务器网段冲突。
五、微服务部署到 K8s(实战 yaml 配置)
K8s 通过 yaml 文件定义资源(Pod、Service、Ingress 等),下面以 user-service 为例,编写完整的部署配置。
5.1 编写 Deployment.yaml(部署微服务)
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service # Deployment名称
namespace: microservice # 命名空间,建议按项目划分
spec:
replicas: 2 # 副本数,生产环境至少2个
selector:
matchLabels:
app: user-service # 匹配Pod的标签
template:
metadata:
labels:
app: user-service # Pod标签
spec:
containers:
- name: user-service # 容器名称
image: harbor.example.com/microservice/user-service:1.0.0 # 镜像地址
imagePullPolicy: IfNotPresent # 镜像拉取策略:本地有则不用拉取
ports:
- containerPort: 8080 # 容器端口,与微服务端口一致
resources: # 资源限制,避免单个Pod占用过多资源
requests:
cpu: "500m" # 最小CPU需求(0.5核)
memory: "512Mi" # 最小内存需求
limits:
cpu: "1000m" # 最大CPU限制(1核)
memory: "1024Mi" # 最大内存限制
livenessProbe: # 存活探针:检测应用是否存活
httpGet:
path: /actuator/health/liveness # Spring Boot Actuator端点
port: 8080
initialDelaySeconds: 60 # 启动后延迟60秒开始检测
periodSeconds: 10 # 检测间隔10秒
readinessProbe: # 就绪探针:检测应用是否准备好接收请求
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 30
periodSeconds: 5
env: # 环境变量配置(也可以用ConfigMap/Secret)
- name: SPRING_PROFILES_ACTIVE
value: "prod"
- name: DB_HOST
value: "mysql-service"
关键说明:
- 命名空间:用
namespace: microservice划分项目,避免资源冲突。- 探针配置:Spring Boot 应用需要引入
spring-boot-starter-actuator依赖,否则探针会失败。- 资源限制:根据微服务的实际需求调整,避免资源浪费或不足。
5.2 编写 Service.yaml(暴露微服务)
Deployment 部署后,Pod 的 IP 是动态变化的,需要用 Service 暴露服务,提供固定访问地址:
yaml
apiVersion: v1
kind: Service
metadata:
name: user-service
namespace: microservice
spec:
selector:
app: user-service # 匹配上面Deployment的Pod标签
ports:
- port: 80 # Service端口
targetPort: 8080 # 映射到Pod的端口
type: ClusterIP # 集群内部访问,外部访问用Ingress
5.3 编写 Ingress.yaml(外部访问入口)
如果需要从集群外部访问微服务,用 Ingress 配置域名和路由:
yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: microservice-ingress
namespace: microservice
annotations:
kubernetes.io/ingress.class: "nginx" # 指定Ingress控制器为Nginx
spec:
rules:
- host: user.example.com # 访问域名
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: user-service # 映射到上面的Service
port:
number: 80
注意:需要先安装 Ingress Nginx 控制器:
bash
运行
bashkubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.5.1/deploy/static/provider/cloud/deploy.yaml
5.4 执行部署命令
bash
# 创建命名空间
kubectl create namespace microservice
# 部署Deployment
kubectl apply -f Deployment.yaml
# 部署Service
kubectl apply -f Service.yaml
# 部署Ingress
kubectl apply -f Ingress.yaml
# 查看部署状态
kubectl get pods -n microservice
kubectl get svc -n microservice
kubectl get ingress -n microservice
5.5 验证访问
bash
# 集群内部访问(Master节点执行)
curl http://user-service.microservice.svc.cluster.local:80/health
# 外部访问(需要在本地DNS或hosts文件中配置域名解析)
curl http://user.example.com/health
六、运维实战:监控、日志、更新
6.1 监控:Prometheus+Grafana
1. 安装 Prometheus(监控指标收集)
csharp
# 用helm安装(推荐,简单快捷)
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm install prometheus prometheus-community/prometheus -n monitoring --create-namespace
2. 安装 Grafana(可视化面板)
bash
helm repo add grafana https://grafana.github.io/helm-charts
helm install grafana grafana/grafana -n monitoring
3. 配置 Spring Boot 微服务监控
在微服务的 pom.xml 中添加依赖:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
在 application-prod.yml 中配置:
yaml
management:
endpoints:
web:
exposure:
include: health,info,prometheus # 暴露prometheus端点
metrics:
tags:
application: user-service # 增加应用标签,方便区分
endpoint:
health:
probes:
enabled: true
show-details: always
4. 配置 Prometheus 抓取微服务指标
修改 Prometheus 的配置文件,添加 Job:
yaml
scrape_configs:
- job_name: 'microservice'
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_label_app]
action: keep
regex: .*service # 匹配所有后缀为service的Pod
- source_labels: [__meta_kubernetes_pod_container_port_number]
action: keep
regex: 8080 # 匹配8080端口
5. 导入 Grafana 面板
Grafana 默认用户名 admin,密码可以通过以下命令获取:
ini
kubectl get secret grafana -n monitoring -o jsonpath="{.data.admin-password}" | base64 --decode
登录后导入 Spring Boot 的监控面板(面板 ID:12900),即可看到 JVM 内存、CPU、请求量、响应时间等指标。
6.2 日志收集:ELK Stack
1. 安装 Elasticsearch(存储日志)
csharp
helm repo add elastic https://helm.elastic.co
helm install elasticsearch elastic/elasticsearch -n logging --create-namespace
2. 安装 Logstash(日志处理)
bash
helm install logstash elastic/logstash -n logging
3. 安装 Kibana(日志可视化)
bash
helm install kibana elastic/kibana -n logging
4. 配置微服务日志输出到文件
在 Spring Boot 的 logback-spring.xml 中配置:
xml
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/var/log/user-service.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>/var/log/user-service.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="FILE" />
</root>
5. 配置 Filebeat 收集日志(每个 Worker 节点)
bash
helm install filebeat elastic/filebeat -n logging
修改 Filebeat 配置,指定日志文件路径:
yaml
filebeat.inputs:
- type: filestream
paths:
- /var/log/*.log # 匹配容器日志目录
processors:
- add_kubernetes_metadata:
host: ${NODE_NAME}
matchers:
- logs_path:
logs_path: "/var/log"
output.elasticsearch:
hosts: ["elasticsearch-master:9200"]
6.3 滚动更新与回滚
1. 滚动更新(发布新版本)
bash
# 修改镜像版本
kubectl set image deployment/user-service user-service=harbor.example.com/microservice/user-service:1.0.1 -n microservice
# 查看更新状态
kubectl rollout status deployment/user-service -n microservice
2. 回滚(版本有问题时)
bash
# 查看更新历史
kubectl rollout history deployment/user-service -n microservice
# 回滚到上一版本
kubectl rollout undo deployment/user-service -n microservice
# 回滚到指定版本
kubectl rollout undo deployment/user-service --to-revision=1 -n microservice
七、八年开发避坑指南(精华总结)
- Docker 镜像不要用 latest 标签:生产环境一定要指定具体版本,否则会导致不同环境镜像不一致,排查困难。
- K8s 资源配置要合理:JVM 的 - Xmx 不能超过 Pod 的 memory limits,否则会被 OOM 杀死;CPU limits 不要设置过高,避免资源浪费。
- 探针配置不能少:livenessProbe 和 readinessProbe 一定要配置,否则 K8s 无法判断应用状态,会导致流量分发到故障节点。
- 镜像仓库一定要搭建:不要依赖 Docker Hub,私有镜像用 Harbor,支持权限管理和镜像扫描,安全性更高。
- 网络插件选择 Calico:Flannel 功能简单,Calico 支持网络策略、Pod 间通信控制,生产环境更推荐。
- 监控和日志必须到位:没有监控就像开车没仪表盘,出现问题无法快速定位;日志要集中收集,方便排查问题。
- 避免单节点故障:Master 节点建议搭建高可用(至少 2 个),Worker 节点至少 2 个,避免单点故障导致服务不可用。
- 不要直接操作 Pod:Pod 是临时的,要通过 Deployment 管理,否则 Pod 重启后配置会丢失。
八、总结与展望
Docker+K8s 已经成为微服务部署的标准方案,本文从环境搭建、容器化、K8s 部署到运维监控,覆盖了全流程的实战细节。作为 Java 开发,我们不需要成为 K8s 专家,但必须掌握核心用法,才能让微服务真正落地生产。
后续可以进一步优化的方向:
- 集成 CI/CD:用 Jenkins 或 GitLab CI 实现代码提交后自动构建镜像、部署到 K8s,实现持续部署。
- 服务网格:引入 Istio,实现流量控制、熔断降级、链路追踪等高级功能。
- 存储方案:对于需要持久化存储的微服务(如数据库),可以使用 K8s 的 PV/PVC 机制,结合 NFS 或云存储。
如果大家在部署过程中遇到问题,欢迎在评论区留言,我会第一时间回复。也欢迎大家分享自己的实战经验,一起交流进步!