DevOps 实验项目阶段 6 笔记:Harbor 私有仓库 + Kubernetes 部署 Spring Boot

阶段目标

完成:

Windows IDEA 开发 Spring Boot
    ↓
Docker 构建镜像
    ↓
推送 Harbor
    ↓
Kubernetes 拉取私有镜像
    ↓
Spring Boot 运行在 K8S
    ↓
Pod 连接 MySQL
    ↓
Service 对外暴露访问

一、Harbor 部署

1. 环境

服务器IP用途
infra192.168.205.146MySQL + Harbor

2. 安装目录

sudo mkdir -p /opt/harbor
sudo chown -R $USER:$USER /opt/harbor
cd /opt/harbor

3. 下载 Harbor

wget https://github.com/goharbor/harbor/releases/download/v2.11.2/harbor-offline-installer-v2.11.2.tgz
tar zxvf harbor-offline-installer-v2.11.2.tgz
cd harbor

二、Harbor 配置

cp harbor.yml.tmpl harbor.yml
vim harbor.yml

1. 修改 hostname

原:

hostname: reg.mydomain.com

改:

hostname: 192.168.205.146

2. 使用 HTTP(实验环境关闭 HTTPS)

注释掉 HTTPS 部分:

#https:
#  port: 443
#  certificate:
#  private_key:

保留 HTTP:

http:
  port: 80

3. 设置管理员密码

harbor_admin_password: Harbor@123

三、安装 Harbor

sudo ./prepare
sudo ./install.sh

查看容器:

docker ps

正常应看到:

harbor-core
harbor-db
harbor-jobservice
harbor-portal
registry
nginx
redis

访问地址:http://192.168.205.146

字段
用户名admin
密码Harbor@123

四、创建 Harbor 项目

配置
项目名称devops
访问级别私有
容量-1 GiB(不限)
镜像代理关闭

最终镜像地址格式:

192.168.205.146/devops/<镜像名>:<标签>

五、配置 Docker 访问 HTTP Harbor

因为 Harbor 使用 HTTP(http://192.168.205.146),而 Docker 默认使用 HTTPS。

需要在 /etc/docker/daemon.json 中配置:Windows端在Docker Desktop中配置

{
  "registry-mirrors": ["https://docker.m.daocloud.io"],
  "insecure-registries": ["192.168.205.146"]
}

重启 Docker:

sudo systemctl daemon-reload
sudo systemctl restart docker

六、Windows Docker 推送 Spring Boot 镜像

1. Docker Desktop 配置

Docker Engine 增加:

{
  "insecure-registries": ["192.168.205.146"]
}

2. 登录 Harbor

docker login 192.168.205.146
字段
Usernameadmin
PasswordHarbor@123

成功输出:Login Succeeded

3. 查看本地镜像

docker images

例如:

devops-demo   v1   xxxxxxxx

4. 修改镜像标签

格式:Harbor地址/项目/镜像:版本

docker tag devops-demo:v1 192.168.205.146/devops/devops-demo:v1

5. 推送到 Harbor

docker push 192.168.205.146/devops/devops-demo:v1

成功输出:

v1: digest: sha256:xxxx

Harbor 页面可见:

项目镜像仓库Tag
devopsdevops-demov1

七、Kubernetes 创建 Harbor Secret

在 k8s-master(192.168.205.141)上执行:

kubectl create secret docker-registry harbor-secret \
  --docker-server=192.168.205.146 \
  --docker-username=admin \
  --docker-password=Harbor@123 \
  --docker-email=admin@example.com

查看:

kubectl get secret

结果:

harbor-secret   kubernetes.io/dockerconfigjson

八、Kubernetes 部署 Spring Boot

目录

mkdir ~/devops-demo-k8s
cd ~/devops-demo-k8s

Deployment 文件

创建 devops-demo-deployment.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: devops-demo
spec:
  replicas: 2
  selector:
    matchLabels:
      app: devops-demo
  template:
    metadata:
      labels:
        app: devops-demo
    spec:
      imagePullSecrets:
        - name: harbor-secret
      containers:
        - name: devops-demo
          image: 192.168.205.146/devops/devops-demo:v1
          imagePullPolicy: Always
          ports:
            - containerPort: 8080
          env:
            - name: SPRING_DATASOURCE_URL
              value: "jdbc:mysql://192.168.205.146:3306/devops_demo?useSSL=false&serverTimezone=Asia/Shanghai"
            - name: SPRING_DATASOURCE_USERNAME
              value: "root"
            - name: SPRING_DATASOURCE_PASSWORD
              value: "123456"

部署:

kubectl apply -f devops-demo-deployment.yml

查看:

kubectl get pods -o wide

成功:

devops-demo-xxxx   1/1 Running   node1
devops-demo-yyyy   1/1 Running   node2

九、解决 ImagePullBackOff

问题

Failed to pull image
https://192.168.205.146/v2/
443 connection refused

原因

Kubernetes 节点默认使用 HTTPS 访问镜像仓库,但 Harbor 使用 HTTP。

解决

在所有 K8S 节点(k8s-master、k8s-node1、k8s-node2)上配置 Docker:

{
  "insecure-registries": ["192.168.205.146"]
}

重启 Docker:

sudo systemctl daemon-reload
sudo systemctl restart docker

十、创建 Kubernetes Service

创建 devops-demo-service.yml

apiVersion: v1
kind: Service
metadata:
  name: devops-demo-service
spec:
  type: NodePort
  selector:
    app: devops-demo
  ports:
    - port: 8080
      targetPort: 8080
      nodePort: 30881

创建:

kubectl apply -f devops-demo-service.yml

查看:

kubectl get svc

结果:

devops-demo-service   NodePort   8080:30881/TCP

访问:

http://192.168.205.142:30881
http://192.168.205.143:30881

十一、解决 MySQL Access Denied

错误

Access denied for user 'root'@'192.168.205.142'

排查

进入 MySQL 容器检查用户权限:

docker exec -it mysql8 mysql -uroot -p
SELECT user, host FROM mysql.user;

发现 root@% 已存在。

根本原因

Spring Boot 配置的密码是 Devops@123,但 MySQL 实际密码是 123456

修复

修改 Deployment 中的环境变量:

- name: SPRING_DATASOURCE_PASSWORD
  value: "123456"

重新部署:

kubectl apply -f devops-demo-deployment.yml
kubectl rollout restart deployment devops-demo

十二、最终验证

查看 Pod

kubectl get pods -o wide

结果:

devops-demo-xxxx   Running   node1
devops-demo-yyyy   Running   node2

测试接口

curl http://192.168.205.142:30881/api/users

返回:

[
  {
    ...
  }
]

说明完整链路成功。


十三、阶段 6 最终架构

Windows IDEA
    │
    │ Docker Build
    ▼
   Harbor (192.168.205.146)
    │
    │ imagePullSecret
    ▼
   Kubernetes
    │
    ├─────────────┐
    │             │
  node1         node2
    │             │
   Pod           Pod
    │
   Spring Boot
    │
   MySQL (192.168.205.146)

十四、阶段 6 完成清单

项目状态
Harbor 部署
Harbor 项目创建
Docker 推送镜像
Kubernetes Secret
Kubernetes 拉取私有镜像
Deployment 部署
Pod 多副本运行
Service 暴露
Spring Boot 访问 MySQL
CRUD 接口验证成功

十五、Systemd 管理 Harbor 自启(运维拓展)

1. Harbor 是怎么跑起来的?

sudo ./install.sh 的本质:

# install.sh 内部做的事情
sudo ./prepare          # 根据 harbor.yml 生成 docker-compose.yml
docker compose up -d    # 启动所有容器

Harbor 不是一个 systemd 原生服务,它是一个 Docker Compose 项目,由 8 个容器组成:

harbor-core
harbor-db
harbor-jobservice
harbor-portal
registry
nginx
redis

2. Harbor 挂了如何启动?

场景 1:容器还在,只是停了
cd /opt/harbor/harbor
sudo docker compose start
场景 2:想连配置一起重新拉起
cd /opt/harbor/harbor
sudo docker compose up -d
场景 3:改过 harbor.yml 后重启
cd /opt/harbor/harbor
sudo docker compose down -v    # 停并删容器,数据不丢
vim harbor.yml                 # 改配置
sudo ./prepare                 # 重新生成配置
sudo docker compose up -d      # 重新拉起
场景 4:宿主机重启后 Harbor 没起来
cd /opt/harbor/harbor
sudo ./prepare          # 可选,配置没变可跳过
sudo docker compose up -d

3. 为什么 restart: always 不够?

Harbor 的每个容器确实设置了 restart: always,但 Docker 重启后仍然起不来,原因有三:

原因 1:Compose 项目网络丢失
Docker 重启前:
  harbor_harbor 网络存在
    ├── harbor-core
    ├── harbor-db
    └── ...

Docker 重启后:
  harbor_harbor 网络被清除
  容器尝试重启但网络不存在
  → 全部启动失败
原因 2:容器启动顺序被打乱

Harbor 的 8 个容器有严格依赖顺序:

harbor-db → redis → registry → harbor-core → jobservice/portal/nginx

restart: always 只能让每个容器各自重试,不会遵守 Compose 的依赖顺序。

原因 3:类比理解
restart: always = 每条鱼自己努力游回鱼缸
docker compose up -d = 管家重新把鱼缸布置好,再把鱼放回去

4. 正确方案:Systemd 管理 Harbor

创建 service 文件
sudo vim /etc/systemd/system/harbor.service

内容:

[Unit]
Description=Harbor Container Stack
After=docker.service
Requires=docker.service

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/harbor/harbor
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
StandardOutput=journal

[Install]
WantedBy=multi-user.target
启用并测试
sudo systemctl daemon-reload
sudo systemctl enable harbor
sudo systemctl start harbor
sudo systemctl status harbor
启动链路
系统开机
    │
    ▼
systemd 启动 Docker(docker.service)
    │
    │ After=docker.service → 等 Docker 完全就绪
    │ Requires=docker.service → Docker 必须活着
    ▼
systemd 启动 Harbor(harbor.service)
    │
    │ ExecStart=/usr/bin/docker compose up -d
    ▼
Docker Compose 重建网络 + 按顺序启动 8 个容器

5. Systemd 单元文件三个区块

[Unit] 区块——描述和依赖
参数作用本例用法
Description给人看的描述Harbor Container Stack
After启动顺序Docker 先起
Requires强依赖Docker 挂了 Harbor 也得停
[Service] 区块——怎么运行
参数作用本例用法
Type=oneshot一次性任务,执行完就退出docker compose up -d 执行完就退
RemainAfterExit=yes退出后仍标记为 active防止 systemd 误判为 failed
ExecStart启动命令docker compose up -d
ExecStop停止命令docker compose down
[Install] 区块——何时自启
参数作用本例用法
WantedBy=multi-user.target系统正常启动时自动启用开机自启

6. 三种常用 Systemd Unit 类型

类型后缀作用你项目里的例子
Service.service管理进程docker.servicecri-docker.serviceharbor.service
Socket.socket管理端口/Unix Socketcri-docker.socket
Timer.timer定时任务还没用到
你项目里的组合

Service + Socket(cri-dockerd):

cri-docker.socket(监听 /run/cri-dockerd.sock)
        │ 有连接进来时激活
        ▼
cri-docker.service(启动 cri-dockerd 进程)

纯 Service(Docker、Harbor):

docker.service(直接管理 dockerd 进程)
harbor.service(执行 docker compose up -d)

7. Harbor 起不来的排障清单

# 看哪个容器挂了
cd /opt/harbor/harbor
sudo docker compose ps

# 看核心组件日志
sudo docker compose logs -f harbor-core
sudo docker compose logs -f nginx
sudo docker compose logs -f harbor-db

# 看宿主机端口是否被占
ss -tlnp | grep -E ':80|:443'

# 看磁盘是否满了
df -h

常见坑:

问题原因解决
nginx 容器起不来80 端口被占用检查宿主机其他 web 服务
harbor-db 一直重启数据盘满了df -h 清理磁盘
客户端登不上insecure-registries 没配检查 /etc/docker/daemon.json

8. 标准恢复 SOP

Harbor 挂了,记住这套命令就够了:

cd /opt/harbor/harbor
sudo docker compose ps        # 看状态
sudo docker compose up -d     # 起

如果改过配置:

cd /opt/harbor/harbor
sudo ./prepare
sudo docker compose up -d

如果想一劳永逸解决重启问题:

# 创建 systemd 服务
sudo vim /etc/systemd/system/harbor.service
# 粘贴上面的 service 内容
sudo systemctl daemon-reload
sudo systemctl enable harbor
sudo systemctl start harbor

十六、下一阶段

阶段 7:Jenkins CI/CD

目标:把现在的手动流程:

手动 docker build
手动 push Harbor
手动 kubectl apply

变成自动化流水线:

Gitee Push
    ↓
Jenkins
    ↓
Maven Build
    ↓
Docker Build
    ↓
Push Harbor
    ↓
kubectl rollout
    ↓
Kubernetes 自动发布

更多推荐