容器&微服务控制(持续更新,部分目录待完善)
目录
1.2.2容器运行时(Container Runtime) 直接交互的工具
1.2.3 容器运行时(Container Runtime)交互工具的使用
文章目录
前言
在数字化转型的浪潮中,容器技术已经成为现代应用开发和部署的事实标准。但很多人对容器的理解仍停留在"Docker"层面,而忽视了支撑容器运行的底层引擎——容器运行时,以及构建在其上的完整技术体系——云原生。本文将深入探讨这三者之间的关联,为您揭开现代应用架构的神秘面纱。
云原生定义
云原生是一套构建和运行应用程序的方法论,它充分利用云计算的优势,实现应用的弹性、可观测性和韧性。
云原生技术栈
应用定义层: Helm、Kustomize
编排调度层: Kubernetes、Nomad
服务网格: Istio、Linkerd
可观测性: Prometheus、Grafana、Jaeger
CI/CD: Jenkins、GitLab CI、ArgoCD
运行时层: 容器运行时
基础设施层: 云平台、物理服务器
一、运行时
1.1OCI 运行时(OCI Runtime):
是符合 OCI(Open Container Initiative,开放容器倡议)标准 的底层工具,负责直接与操作系统内核交互,创建并运行容器进程。它是容器化的 “执行引擎”,仅关注容器的初始化(如创建 namespace、cgroup、挂载文件系统等),完成后即退出,不负责长期管理。典型代表:runc(Go 语言实现,应用最广)、crun(C 语言实现,更轻量)、youki(Rust 语言实现,安全性更强)
1.2容器运行时(Container Runtime):
是完整的容器生命周期管理系统,负责镜像管理、容器创建 / 启动 / 停止 / 删除、网络配置、存储挂载等全流程操作,通常包含对 OCI 运行时的调用。它是上层工具(如 docker、podman 命令,或 K8s 的 kubelet)与底层 OCI 运行时之间的中间层。典型代表:containerd、CRI-O、Docker Engine(底层依赖 containerd)、Podman(无守护进程的运行时)
1.2.1主流容器运行时核心定位与设计背景
| 运行时 | 核心定位 | 开发 / 维护方 | 设计初衷 |
|---|---|---|---|
| containerd | 通用容器运行时,专注于容器生命周期管理,原生支持 CRI 标准 | Docker 公司(后捐给 CNCF) | 从 Docker Engine 中剥离的核心组件,追求轻量、稳定,成为容器运行时的事实标准 |
| cri-o | 专为 Kubernetes 设计的轻量容器运行时,完全遵循 CRI 标准,无 Docker 依赖 | Red Hat 主导,CNCF 孵化项目 | 提供 K8s 专属的极简运行时,移除 Docker 相关冗余组件,优化 K8s 集成体验 |
1.2.2容器运行时(Container Runtime) 直接交互的工具
| 工具名称 | 核心定位 | 支持的容器运行时 | 遵循标准 | 主要功能 | 适用场景 | 特点总结 |
|---|---|---|---|---|---|---|
| crictl | K8s 生态的 CRI 接口客户端 | containerd、CRI-O、cri-dockerd(Docker) | CRI(K8s 定义) | 管理容器(启停、查询)、镜像(拉取、删除)、Pod 沙箱 | K8s 节点上的容器调试与管理 | 轻量、专注 CRI 接口,与 K8s 生态深度契合,(无构建、编排功能) |
| nerdctl | containerd 的 Docker 风格命令行工具 | containerd(原生) | OCI、CRI(可选) | 完全模拟 docker 命令(run/ps/build 等),支持 Compose | 单机或 K8s 环境中用 Docker 习惯操作 containerd | 兼容 Docker 命令, 支持镜像构建(build)、Compose 编排、网络 / 存储管理等高级功能 |
| docker | Docker 引擎的原生命令行工具 | Docker Engine(依赖 containerd) | Docker API、OCI | 全功能容器 / 镜像管理,支持构建、网络、卷、Compose | 单机 Docker 环境,开发者日常使用 | 支持镜像构建(build)、Compose 编排、网络 / 存储管理等高级功能,但依赖 Docker 引擎 |
| podman | 无守护进程的 Docker 兼容工具 | 自身集成(依赖 runc/crun) | OCI、CRI(可选) | 命令与 docker 完全兼容,支持 rootless 容器 | 追求安全(rootless)、无守护进程的单机环境 | 无 daemon 设计,安全性高,兼容 Docker 命令 |
| ctr | containerd 的原生调试工具 | containerd(原生) | containerd 私有接口 | 直接操作 containerd 内部组件(命名空间、快照等) | containerd 底层调试与高级配置 | 功能基础,偏底层,适合调试 containerd 本身 |
| crioctl | CRI-O 的专用命令行工具 | CRI-O | CRI | 管理 CRI-O 运行时的容器、镜像、Pod 沙箱 | 纯 CRI-O 环境的容器管理 | 专为 CRI-O 设计,功能类似 crictl 但更专用,(无构建、编排功能) |
1.2.3 容器运行时(Container Runtime)交互工具的使用
备注:由于docker工具安装即用,在此不做介绍,主要介绍主流且功能强大的下述两者,使用工具前需要知道自己采用的容器时是哪一种,然后进行配置。
(1)crictl
如果你的节点已安装 Docker,旧版本支持docker-shim的docker不要额外配置,新版本docker需通过 cri-dockerd 适配 CRI 接口,centos 7 需要安装下载对应 el7 版本的 cri-dockerd 包(而非 el8),注:尽量后续使用rock-linux替代centos
wget https://github.com/Mirantis/cri-dockerd/releases/download/v0.3.1/cri-dockerd-0.3.1-3.el7.x86_64.rpm # 重新安装 sudo rpm -ivh cri-dockerd-0.3.1-3.el7.x86_64.rpm #手动创建 cri-dockerd 的 systemd 服务文件 vim /usr/lib/systemd/system/cri-dockerd.service [Unit] Description=CRI Interface for Docker Application Container Engine Documentation=https://docs.mirantis.com After=network-online.target firewalld.service docker.service Wants=network-online.target Requires=docker.service [Service] Type=notify ExecStart=/usr/bin/cri-dockerd \ --container-runtime-endpoint unix:///var/run/cri-dockerd.sock\ --network-plugin=cni \ --cni-conf-dir=/etc/cni/net.d \ --cni-bin-dir=/opt/cni/bin ExecReload=/bin/kill -s HUP $MAINPID TimeoutSec=0 RestartSec=2 Restart=always StartLimitBurst=3 StartLimitInterval=60s LimitNOFILE=infinity LimitNPROC=infinity LimitCORE=infinity TasksMax=infinity Delegate=yes KillMode=process [Install] WantedBy=multi-user.target
启动cri-dockerd服务并配置crictl工具配置,指定sock:
systemctl start cri-dockerd
#创建 / 修改 crictl 配置文件
vim /etc/crictl.yaml
# 对接 Docker 内置 containerd 的套接字
runtime-endpoint: unix:////run/cri-dockerd.sock
image-endpoint: unix:////run/cri-dockerd.sock
timeout: 10 # 超时时间,单位秒
debug: false # 关闭调试模式(如需排查问题可设为 true)
crictl使用:
[root@k8s-master ~]# ./crictl ps
CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID POD NAMESPACE
[root@k8s-master ~]# ./crictl images
IMAGE TAG IMAGE ID SIZE
docker.1ms.run/registryk8s/descheduler-descheduler v0.19.0 71eea72eaa850 43MB
socp.io/zb/descheduler v0.19.0 71eea72eaa850 43MB
docker.1ms.run/registryk8s/descheduler-descheduler v0.21.1 ae9a3bdbaf6a5 58.8MB
socp.io/zb/descheduler v0.21.1 ae9a3bdbaf6a5 58.8MB
docker.1ms.run/registryk8s/descheduler-descheduler v0.22.1 8591902620c10 61MB
socp.io/zb/descheduler v0.22.1 8591902620c10 61MB
备注:如果使用的是containerd容器运行时,containerd 默认未启用 CRI 插件,需手动生成配置并修改,/etc/crictl.yaml中的sock不一样!!!:
# 1. 生成默认配置文件(默认路径 /etc/containerd/config.toml)
containerd config default > /etc/containerd/config.toml
# 2. 编辑配置文件,确保 CRI 插件启用(关键步骤!)
vim /etc/containerd/config.toml
#验证 containerd 套接字是否存在
ls -l /run/containerd/containerd.sock # 应输出类似 "srw-rw---- 1 root root 0 ... /run/containerd/containerd.sock"
#修改crictl 配置文件
vim /etc/crictl.yaml
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
timeout: 10
debug: false
#如果是CRI-O运行时:
runtime-endpoint: unix:///run/crio/crio.sock
image-endpoint: unix:///run/crio/crio.sock
timeout: 10
debug: false
(2)podman(工具也是服务)
如果是使用的centos7系统,安装过高版本docker环境的,需要卸载containerd.io,它是centos8的默认运行时,centos8和7已经不再维护。通过 yum install 指定 runc 版本,并通过 --setopt 选项禁止 yum 用 containerd.io 取代 runc,podman默认用runc。注:尽量后续使用rock-linux替代centos
sudo yum install -y --setopt=obsoletes=0 runc-1.0.0-70.rc10.el7_9.x86_64
[root@k8s-master ~]# podman load -i deschedulerimage19.tar.gz
Getting image source signatures
Copying blob bfd14c139152 done
Copying config 71eea72eaa done
Writing manifest to image destination
Storing signatures
Loaded image(s): socp.io/zb/descheduler:v0.19.0
[root@k8s-master ~]#
[root@k8s-master ~]#
[root@k8s-master ~]# podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
socp.io/zb/descheduler v0.19.0 71eea72eaa85 5 years ago 43 MB
二、容器服务调用链
备注:container-shim 的设计目的是解耦高层运行时(containerd)与底层容器进程
docker-shim:早期 Docker 引擎中使用的垫片,随 Docker 一起发布,用于衔接 dockerd 和 runc。
container-shim:containerd 独立后推出的通用垫片,功能与 docker-shim 类似,但更标准化,目前已替代 docker-shim 成为主流。
2.1 k8s+docker+containerd
K8s中采用docker的containerd作为容器运行时:其中cri-dockerd是 “翻译器”,让不支持 CRI 的 Docker 能与遵循 CRI 标准的工具(如 crictl、k8s)通信。
用户/K8s → crictl/kubelet(CRI 请求)→ cri-dockerd → dockerd → containerd → container-shim → OCI 运行时(runc/crun)→ 容器进程
2.2 k8s + cri-o
K8s 控制平面 → kubelet → CRI 接口 → CRI-O → container-shim(crio-shim)→ OCI 运行时(runc/crun)→ 容器进程
2.3 docker+containerd
docker 命令 → dockerd → containerd → containerd-shim-runc-v2 → OCI 运行时(runc/crun) → 容器进程
2.4 podman
用户 → podman 命令 → 容器运行时(runc/crun)→ 容器进程
↓
(可选)OCI 钩子 / 垫片```
例如:podman 命令 → conmon → runc/crun → 容器进程
↑ ↓
└────────────────┘(conmon 接管为容器父进程)
具体流程解析:
1. **用户层**:通过 `podman run`、`podman start` 等命令发起操作。
2. **Podman 引擎层**:
- 直接解析命令并处理镜像拉取、容器配置(如网络、存储、资源限制)等逻辑。
- 生成符合 OCI 标准的容器配置文件(`config.json`)。
3. **运行时层**:
- 调用底层 OCI 运行时(默认 `crun` 或 `runc`),将配置文件传递给运行时。
- 运行时(如 `crun`)负责与 Linux 内核交互,创建 namespace、cgroup 等隔离环境,最终启动容器进程。
4. **容器进程**:在隔离环境中运行用户指定的应用程序。
### 二、与 Docker 调用链的核心差异
| 维度 | Podman 调用链 | Docker 调用链 |
|---------------------|----------------------------------------|----------------------------------------|
| 守护进程 | 无(`podman` 是单机命令,直接调用运行时) | 依赖 `dockerd` 守护进程作为中间层 |
| 中间组件 | 无额外垫片(运行时直接作为容器父进程) | 需 `containerd` + `containerd-shim` 垫片 |
| 权限处理 | 直接以用户身份(或 root)执行操作 | 所有操作通过 `dockerd` 权限代理 |
### 三、特殊场景的调用链扩展
1. **使用 Pod 功能时**:
当创建 Pod(`podman pod create`)时,Podman 会启动一个“基础设施容器(infra container)”作为 Pod 内所有容器的共享网络命名空间,调用链变为:
三、镜像仓库(容器的家)
备注:安装docker等软件yum源小窍门,强制替换/etc/yum.repos.d路径下repo文件里$releasever和$basearch版本变量,$releasever是软件包对应操作系统版本,$basearch是x86_64,cpu架构。
3.1国内容器镜像站点
度度鸟镜像:渡渡鸟镜像同步站
3.2docker registry
Docker Registry用于创建私服版本个人的docker hub,相比Harbor更加轻量,部署更快更容易,便于快速构建临时镜像仓储服务。在上述镜像站点下载Docker Registry:
注意:此处我采取的是registry+ssl自签证书+hosts域名映射+docker-registry-browser页管理

参考优质博客:https://blog.csdn.net/securitit/article/details/109668824Dockerfile Registry 配置HTTPS服务https://blog.csdn.net/securitit/article/details/109668824
参考优质博客:Dockerfile Registry WebUI 之 docker-registry-frontend 高级应用
https://blog.csdn.net/securitit/article/details/109671914
3.2.1 部署方式
(1)制作证书
#证书文件,采取证书config定义文件比命令行生成更规范,对podman使用该镜仓踩坑少
[root@cloudbobo tool]# cat openssl.cnf
[req]
default_bits = 4096 # 与你现有证书的4096位一致
prompt = no
default_md = sha256
distinguished_name = dn
x509_extensions = v3_ca # 启用扩展字段[dn]
C = CN
ST = YourProvince # 与现有证书一致
L = YourCity # 与现有证书一致
O = YourOrg # 与现有证书一致
OU = YourDept # 与现有证书一致
CN = cloudbobo.com # 主域名[v3_ca]
subjectAltName = DNS:cloudbobo.com # 必须添加这行,明确域名
keyUsage = digitalSignature, keyEncipherment # 证书用途(服务器端)
extendedKeyUsage = serverAuth # 限定用于服务器认证
basicConstraints = CA:FALSE # 注意:设为FALSE(你的现有证书误设为CA:TRUE,非根证书无需CA权限,用CA也可,或许更好,更容易通过“严格的,不那么友好的podman"的信任,要不podman不承认该镜仓,无法推拉镜像)
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer:always
证书生成:
#推荐,基于上述配置定义文件生成
openssl req -new -x509 -days 3650 -key /data/my_registry/certs/registry.key -out /data/my_registry/certs/registry.crt -config openssl.cnf
#纯命令行,生成证书
openssl req -newkey rsa:4096 -nodes -sha256 -keyout /data/my_registry/certs/registry.key -x509 -days 36500 -out /data/my_registry/certs/registry.crt -subj "/C=CN/ST=YourProvince/L=YourCity/O=YourOrg/OU=YourDept/CN=cloudbobo.com"
(2)部署仓储服务
docker run -d \
--name registry \
-p 443:5000 \
--restart=always \
-v /data/my_registry/certs/:/securitit/registry/certs \
-v /data/my_registry/data/:/var/lib/registry
-e REGISTRY_HTTP_TLS_CERTIFICATE=/securitit/registry/certs/registry.crt \
-e REGISTRY_HTTP_TLS_KEY=/securitit/registry/certs/registry.key \
docker.bobo.com/registry:v2
#查看容器运行是否成功
[root@cloudbobo tool]# docker ps | grep registry2
30c067109458 cloudbobo.com/registry/registry:v2 "/entrypoint.sh /etc…" 11 hours ago Up 10 hours 0.0.0.0:443->5000/tcp, [::]:443->5000/tcp registry2
#修改/etc/hots域名映射,方便直接通过域名拉取镜像,不再需要指定端口
[root@cloudbobo tool]# tail -1 /etc/hosts
192.168.44.142 cloudbobo.com
#修改docker指定信任私有仓库地址域名
[root@cloudbobo tool]# cat /etc/docker/daemon.json
{
"live-restore": true,
"insecure-registries" : [
"cloudbobo.com"
],
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "10"
},
"data-root": "/data/docker"
}
#检查registry仓库端口,是否有返回
[root@cloudbobo tool]# curl https://cloudbobo.com/v2/_catalog -k
{"repositories":["dpanel/dpanel","moelin/1panel","portainer/portainer","registry/registry"]}
(3)部署仓储Web服务
docker run -d \
--name registryBrowser \
--restart=always \
-p 9091:8080 \
-e DOCKER_REGISTRY_URL=https://192.168.44.142 \
-e NO_SSL_VERIFICATION=true \
-e SECRET_KEY_BASE=miaomiao \
cloudbobo.com/docker-registry-browser:v3
(4)添加podman信任
如果需要使用registry作为docker和podman之间的桥梁,进行镜像推拉,项目建设测试开发等,需要给podman配置无条件信任私有仓库
#将registry的ssl证书复制到podman的证书信任路径中,需要创建域名目录
[root@cloudbobo tool]# ll /etc/containers/certs.d/cloudbobo.com/
总计 4
-rw-r--r--. 1 root root 2346 9月24日 22:48 registry.crt
3.3Harbor
3.3.1 概述
Harbor是一个开源的企业级容器镜像仓库,提供镜像的存储、分发、安全扫描、漏洞分析等功能。它支持多租户管理、基于角色的访问控制(RBAC)、镜像复制、与CI/CD工具集成等特性,适合企业级容器化场景。
3.3.2 核心组件及作用
- Core:核心服务(默认启用),负责处理API请求、UI交互、权限控制等。主要功能包括用户管理、项目管理、镜像仓库管理,需配置harbor.yml(域名、证书、存储路径)。
- PostgreSQL:元数据库(默认启用,v2.0+,默认使用PostgreSQL存储元数据存储如用户信息、项目配置、镜像标签等信息),内嵌于容器,无需额外配置。
- Registry:镜像存储库(默认启用),依赖registry容器,需配置存储后端(本地/S3/OSS等)。
- Nginx:反向代理(默认启用),自动生成配置,需绑定HTTPS证书。
- Job Service:任务管理(默认启用,处理复制和扫描任务)。
- Redis:缓存和消息队列服务(默认启用),用于临时存储会话数据、加速UI响应,以及协调Job Service的异步任务。
可选组件(需手动启用):
- Trivy/Clair:漏洞扫描(需在harbor.yml中启用,下载Trivy离线数据库),Trivy(默认):轻量级扫描工具,集成在Harbor中,支持快速检测镜像漏洞,Clair(旧版):通过独立服务分析镜像层漏洞,需额外部署。
- Notary:镜像签名(需独立配置证书),镜像签名验证组件,提供内容信任(Content Trust)功能,确保镜像来源可信。
部署架构:
- 单节点部署:docker-compose法,所有组件运行在同一主机,适合测试或小规模环境。
- 高可用部署:支持多副本Core、独立数据库集群、外部存储(如S3)等,确保服务可靠性,kubernetes部署等。
3.3.3 部署方式
官方参考:Harbor 文档 | Harbor 2.13 文档 - Harbor 中文 (goharbor.cn)

(1)下载harborGithb项目
Github项目加速下载站点(用于快速下载release包):Github Proxy 文件代理加速 (akams.cn)

(2)制作证书(标准法)
步骤 1:准备 openssl.cnf 配置文件
创建配置文件(如 ./openssl.cnf),定义 CA 根证书和服务器证书的生成规则
[req]
# 1. 基础配置段(控制证书请求和生成的全局规则)
default_bits = 2048 # 生成私钥的默认长度(2048位,平衡安全性和性能)
default_md = sha256 # 签名时默认使用的哈希算法(SHA-256,现代安全标准)
distinguished_name = req_distinguished_name # 引用证书持有者身份信息的配置段
x509_extensions = v3_ca # 生成自签名证书(如CA根证书)时使用的扩展配置段
string_mask = utf8only # 限制证书中字符串字段仅使用UTF-8编码(避免乱码)
prompt = no # 关闭交互式提示,直接使用配置文件或命令行参数中的信息
# 2. 证书持有者身份信息(可根据实际场景修改)
[req_distinguished_name]
C = CN # Country(国家代码,如CN=中国)
ST = Beijing # State or Province(省份/地区)
L = Beijing # Locality(城市)
O = MyCompany # Organization(组织/公司名称)
OU = IT # Organizational Unit(部门,如IT部)
CN = MyCA # Common Name(通用名称,CA根证书此处为CA机构名称)
# 3. CA根证书扩展配置(标记为CA证书,定义其权限)
[v3_ca]
subjectKeyIdentifier = hash # 生成证书持有者公钥的哈希标识(用于证书链验证)
authorityKeyIdentifier = keyid:always,issuer # 生成签发者(CA)的公钥哈希和 issuer 信息(用于追溯信任链)
basicConstraints = critical, CA:true # 标记为CA证书(critical表示该字段不可忽略),允许签发其他证书
keyUsage = critical, digitalSignature, keyCertSign # 允许的密钥用途:数字签名、证书签发(CA核心权限)
extendedKeyUsage = serverAuth, clientAuth # 扩展用途(可选):允许用于服务器和客户端认证
# 4. 服务器证书扩展配置(限制为服务器用途,禁止作为CA)
[server_cert]
subjectKeyIdentifier = hash # 同CA配置,用于标识服务器证书公钥
authorityKeyIdentifier = keyid:always,issuer # 关联签发者(CA)信息,用于客户端验证证书合法性
basicConstraints = critical, CA:false # 禁止作为CA证书(核心安全约束,防止证书被滥用签发其他证书)
keyUsage = critical, digitalSignature, keyEncipherment,dataEncipherment # 允许的密钥用途:数字签名(验证身份)、密钥加密(加密通信)
extendedKeyUsage = critical, serverAuth # 扩展用途:仅允许用于服务器认证(明确证书用途)
subjectAltName = DNS:cloudbobo.io, DNS:*.cloudbobo.io # 服务器域名列表(支持主域名和通配符,比CN更灵活)
步骤 2:生成 CA 根证书(自签名)
CA 根证书是信任链的起点,用于签发服务器 / 客户端证书,操作如下
说明:
ca.key 是 CA 体系的核心机密,需离线存储(如加密 U 盘),泄露会导致整个信任链失效;
ca.crt 是公钥证书,需分发给所有客户端(如 Docker、浏览器),用于验证由该 CA 签发的证书。
# 1. 创建工作目录(集中管理证书文件)
mkdir -p certs && cd certs
# 2. 生成 CA 根证书私钥(ca.key)
# - genrsa:生成RSA私钥
# - out ca.key:输出私钥文件
# - 4096:私钥长度(比默认2048位更高,适合CA根证书长期使用)
openssl genrsa -out ca.key 4096
# 3. 限制私钥权限(仅所有者可读写,防止泄露)
chmod 600 ca.key
# 4. 生成 CA 根证书(自签名,ca.crt)
# - req:生成证书请求或自签名证书
# - x509:直接生成自签名证书(跳过CSR步骤,CA根证书专用)
# - new:生成新的证书请求(结合x509则为自签名)
# - nodes:不加密私钥(避免每次使用时输入密码,生产环境可移除该参数并设置密码)
# - key ca.key:使用指定的私钥签名
# - days 3650:证书有效期(10年,CA根证书通常长期有效)
# - out ca.crt:输出公钥证书文件
# - config ../openssl.cnf:引用配置文件,应用[v3_ca]扩展
openssl req -x509 -new -nodes \
-key ca.key \
-days 3650 \
-out ca.crt \
-config ./openssl.cnf
步骤 3:生成服务器证书请求(CSR)
服务器证书需先通过 CSR(证书请求文件)向 CA 申请签名,操作如下:
生成服务器证书请求(CSR)的命令
说明:
CN=harbor.example.com 需与服务器实际域名一致,若需多域名,优先通过配置文件 [server_cert] 的 subjectAltName 定义;server.csr 不含私钥,可安全提交给 CA 用于签发证书。
# 1. 生成服务器证书私钥(server.key)
# - genrsa:生成RSA私钥
# - out server.key:输出服务器私钥文件
# - 2048:私钥长度(符合配置文件default_bits,平衡性能)
openssl genrsa -out server.key 2048
# 2. 限制私钥权限(仅所有者可读写)
chmod 600 server.key
# 3. 生成服务器证书请求(server.csr)
# - req:生成证书请求
# - new:创建新的请求
# - key server.key:使用服务器私钥关联请求
# - out server.csr:输出CSR文件(包含公钥和服务器身份信息)
# - subj:覆盖配置文件中的身份信息(重点修改CN为服务器域名)
# - config ../openssl.cnf:引用配置文件,确保请求格式合规
openssl req -new \
-key server.key \
-out server.csr \
-subj "/C=CN/ST=Beijing/L=Beijing/O=MyCompany/OU=IT/CN=cloudbobo.io" \
-config ./openssl.cnf
步骤 4:用 CA 根证书签发服务器证书
通过 CA 私钥签名服务器 CSR,生成最终可用的服务器证书:
用 CA 签发服务器证书的命令
# 用 CA 根证书签发服务器证书(生成 server.crt)
# - x509:生成X.509格式证书
# - req:处理证书请求
# - in server.csr:输入服务器证书请求文件
# - CA ca.crt:指定签发者(CA)的公钥证书
# - CAkey ca.key:指定CA的私钥(用于签名)
# - CAcreateserial:首次签发时生成序列号文件(ca.srl),确保每个证书序号唯一
# - out server.crt:输出签发生成的服务器证书
# - days 365:证书有效期(1年,服务器证书建议定期轮换)
# - extfile ../openssl.cnf:指定扩展配置文件
# - extensions server_cert:应用[server_cert]扩展(限制证书用途为服务器认证)
openssl x509 -req \
-in server.csr \
-CA ca.crt \
-CAkey ca.key \
-CAcreateserial \
-out server.crt \
-days 3650 \
-extfile ./openssl.cnf \
-extensions server_cert
说明:
核心是通过 -extensions server_cert 强制服务器证书仅用于 serverAuth(服务器认证),且不可作为 CA 证书(CA:false),符合安全规范;生成的 server.crt(公钥)和 server.key(私钥)需部署到服务器(如 Harbor 的 ssl 目录),用于 HTTPS 通信。

(3)docker compose部署
- 修改harbor配置文件,harbor.yml cp harbor.yml.tmpl harbor.yml

特别注意要将hostname 设置为域名,和证书一致,要不最后证书在docker客户端验证不通过

-
下载工具镜像prepare
需要先去渡渡鸟或者毫秒镜像拉取goharbor/prepare:对应harbor版本的工具镜像,并重新打包为:docker.io/goharbor/prepare:vxx版本后才能执行./prepare生成配置文件脚本

-
执行install导入组件镜像并运行
利用install脚本解压缩harbor.v2.13.2.tar.gz镜像集合,该压缩包是所有组件的镜像层集合,执行过程中会边解压边自己docker-load最后会根据./prepare生成的docker-compose.yaml文件run起来


-
优化目录结构(可以不做)
原来的结构config是在执行prepare的目录的common/comfig路径下,去掉common层实现下述效果
/data/harbor/
├── crt/ # 证书目录(手动创建,存 server.crt、server.key)
│ ├── server.crt
│ └── server.key
├── data/ # 所有组件的持久化数据(自动按组件分文件夹)
│ ├── registry/ # Registry 镜像数据
│ ├── database/ # PostgreSQL 数据库数据
│ ├── redis/ # Redis 缓存数据
│ └── jobservice/ # Jobservice 任务数据
├── logs/ # 所有组件的运行日志(自动按组件分文件夹)
│ ├── core/ # Core 服务日志
│ ├── nginx/ # Nginx 访问日志
│ ├── jobservice/ # Jobservice 任务日志
│ └── registry/ # Registry 镜像服务日志
├── config/ # 所有组件的核心配置(自动按组件分文件夹)
│ ├── nginx/ # Nginx 配置
│ ├── core/ # Core 服务配置
│ └── registry/ # Registry 配置
├── harbor.yml # 你的核心配置文件(修改后)
├── docker-compose.yml # 重新生成的 Compose 文件
└── 其他安装文件(install.sh、prepare 等)
先暂停harbor,修改docker-compose.yaml中的volum映射

再将./prepare生成的common里面的配置文件归档复制到/data/harbor/config下,最后再启动服务

(4)添加docker和podman证书信任

[root@cloudbobo harbor]# mkdir /etc/docker/certs.d/cloudbobo.io
[root@cloudbobo harbor]# cp /data/harbor/crt/
ca/ server.crt server.key
[root@cloudbobo harbor]# cp /data/harbor/crt/ca/
ca.crt ca.key ca.srl openssl.cnf server.csr
[root@cloudbobo harbor]# cp /data/harbor/crt/ca/ca.crt /etc/docker/certs.d/cloudbobo.io/
(5)给harbor项目db增加可视化工具pgadmin

毫秒镜像:docker.1ms.run/dpage/pgadmin4
编辑docker-compose.yaml,增加以下内容:
#添加pgadmin后,直接在docker的harbor内网环境下安全连接pgsql
pgadmin:
image: cloudbobo.io/library/pgadmin4:9 # 官方pgAdmin镜像重打包
container_name: harbor-pgadmin
restart: always
environment:
- PGADMIN_DEFAULT_EMAIL=pg@admin.com # 登录pgAdmin的邮箱
- PGADMIN_DEFAULT_PASSWORD=Harbor12345 # 登录pgAdmin的密码(可自定义)
volumes:
- /data/harbor/data/pgadmin:/var/lib/pgadmin # 持久化pgAdmin数据
networks:
- harbor # 加入Harbor默认网络,与postgres容器互通
ports:
- 5050:80
depends_on:
- postgresql # 确保数据库启动后再启动pgAdmin
healthcheck:
test: ["CMD", "sh", "-c", "nc -z localhost 80 || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 90s
执行:docker compose stop ; docker compose start重启所有组件,同时将新加入的pgadmin启动
配置:
四、容器统一平台(可视化管理)
4.1 Dpanel
Dpanel 是一个多功能的面板工具,通常用于服务器管理、网站托管或自动化任务。
4.1.1Dpanel核心功能
- 服务器管理:支持 Linux 和 Windows 服务器的监控、配置及维护。
- 网站托管:提供一键部署网站、SSL 证书管理及域名绑定功能。
- 数据库管理:集成 MySQL、PostgreSQL 等数据库的可视化管理工具。
- 自动化任务:支持定时任务、脚本执行和日志分析。

4.1.2部署Dpanel
docker run \
-p 8807:8080 \
--name dpanel \
--restart=always
-e APP_NAME=dpanel \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /data/dpanel:/dpanel \
-d cloudbobo.comdpanel/dpanel:v2
4.2 Portainer
Portainer 是一个轻量级的容器管理工具,提供基于 Web 的用户界面,用于管理 Docker 和 Kubernetes 环境。它简化了容器化应用的部署、监控和维护,适合开发者和运维人员使用。
4.2.1Portainer核心功能
- 容器管理:查看、启动、停止、删除容器。
- 镜像管理:拉取、构建、删除镜像。
- 网络与存储:配置 Docker 网络和卷。
- 用户权限:支持多用户和角色权限控制。
- 集群支持:兼容 Docker Swarm 和 Kubernetes。

4.2.2部署Portainer
docker run -d \
-p 9000:9000 \
--name portainer \
--restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /data/portainer:/data \
cloudbobo.com/outlovecn/portainer-cn:latest
4.3 1Panel
1Panel 是一个现代化的开源 Linux 服务器运维管理面板,提供可视化操作界面,支持 Web 端管理服务器、容器、数据库、文件等。其定位类似于宝塔面板,但更注重轻量化和容器化部署,适合开发者及运维人员快速搭建和管理服务。
4.3.1 1Panel核心功能
- 服务器监控:实时查看 CPU、内存、磁盘等资源使用情况。
- 容器管理:集成 Docker,支持容器及镜像的创建、启动、停止等操作。
- 网站管理:支持 Nginx、PHP、MySQL 等环境的快速部署。
- 文件管理:内置文件浏览器,支持上传、下载、编辑等操作。
- 数据库管理:可视化操作 MySQL、PostgreSQL 等数据库。
- 计划任务:通过界面配置定时任务(如备份、脚本执行)。

4.3.2 部署1Panel
docker run -d \
--name 1panel \
-p 8888:10086 \
-v /data/1panel:/data/ \
-v /var/run/docker.sock:/var/run/docker.sock \
cloudbobo.com/moelin/1panel
五、docker服务(安装相关问题)
5.1docker、docker-compose安装教程
5.1.1 卸载旧版
#查看当前旧版本安装情况
dnf list installed | grep docker
#删除安装过docker的相关包
dnf -y remove containerd.io.x86_64 \ docker-buildx-plugin.x86_64 \ docker-ce.x86_64 \ docker-ce-c
dnf remove -y docker*
#删除docker相关的镜像和容器,进入 /var/lib 目录,删除 docker 目录,这是存放容器和镜像的目录
rm -rf docker
5.1.2 docker安装
5.1.2.1安装方式一:dnf/yum方式
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum install -y yum-utils
yum list docker-ce --showduplicates | sort -r

备注:安装最新版本直接执行:dnf -y install docker-ce
如果想要安装旧版本或者历史版本:版本号只要“:”后面的那部分
yum install docker-ce-23.0.3-1.el7 docker-ce-23.0.3-1.el7 containerd.io

5.1.2.2安装方式二:二进制包方式
1.下载docker二进制包
arm:官方下载地址:https://download.docker.com/linux/static/stable/aarch64/
x86:官方下载地址: https://download.docker.com/linux/static/stable/x86_64/
2.使用wget下载或者直接打开网站下载对应版本到服务器,如:19.03.8
wget https://download.docker.com/linux/static/stable/x86_64/docker-19.03.8.tgz
或者:

3.解压下载好的docker包
tar zxf docker-19.03.8.tgz
4.将docker目录下的二进制文件移动到/usr/bin/
mv docker/* /usr/bin/
5.测试docker
docker
6.编写docker守护进程daemon.json(insecure-registries:[私有镜像仓库地址])
[root@localhost sophon]# cat /etc/docker/daemon.json
{
"exec-opts": ["native.cgroupdriver=systemd"],
"graph": "/data/docker_storage",
"log-driver": "json-file",
"log-opts": {
"max-size": "100m"
},
"storage-driver": "overlay2",
"storage-opts": [
"overlay2.override_kernel_check=true"
],
"insecure-registries" : ["192.168.4.17:8090","111.136.254.111:8090"],
"registry-mirrors": ["https://g427vmjy.mirror.aliyuncs.com"],
"live-restore": true
}
7.编写docker.server,配置开机启动
[root@localhost sophon]# cat /lib/systemd/system/docker.service
[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
After=network-online.target firewalld.service
Wants=network-online.target
[Service]
Type=notify
EnvironmentFile=-/etc/sysconfig/docker
EnvironmentFile=-/etc/sysconfig/docker-storage
EnvironmentFile=-/etc/sysconfig/docker-network
Environment=GOTRACEBACK=crash
ExecStart=/usr/bin/dockerd -H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock
ExecReload=/bin/kill -s HUP $MAINPID
LimitNOFILE=1048576
LimitNPROC=1048576
LimitCORE=infinity
# set delegate yes so that systemd does not reset the cgroups of docker containers
Delegate=yes
# kill only the docker process, not all processes in the cgroup
KillMode=process
[Install]
WantedBy=multi-user.target
8.启动docker服务
systemctl daemon-reload
systemctl restart docker
systemctl enable docker
六、docker容器(宿主机:容器目录持久化常见问题)
Docker 容器内的进程默认使用非 root 用户(如 alertmanager 容器用 nobody 或专用用户,UID 通常为 65534 或 1000 等),而宿主机目录默认权限可能为 root:root(UID=0)或其他用户,导致容器内用户无读写权限,容器进程没有宿主机持久化目录的写入权限,日志信息反复提示:映射的目录读写permission denied导致容器报错:
解决方案:
1. 提前创建宿主机目录并匹配 UID
核心思路:在宿主机手动创建挂载目录,修改其所有者 UID 与容器内进程的 UID 一致(无需知道用户名,只需 UID 匹配)。
步骤:
① 查看容器内进程的 UID(以 alertmanager 为例):
bash
# 临时启动容器查看用户 UID
docker run --rm --entrypoint id prom/alertmanager:v0.28.0
# 输出示例:uid=1000(alertmanager) gid=1000(alertmanager) ...
# 可知容器内用户 UID=1000
② 在宿主机创建目录并修改权限:
bash
# 创建持久化目录(配置、数据分开更清晰)
mkdir -p /data/alertmanager/{config,data}
# 修改目录所有者 UID 为容器内 UID(1000)
sudo chown -R 1000:1000 /data/alertmanager
# 赋予读写权限(无需 777,644 或 755 即可)
sudo chmod -R 755 /data/alertmanager
七、打包docker容器镜像(基于alert的webhook服务器)
1. 配置文件 (config.yaml)
# 数据库配置
database:
host: "localhost"
port: 5432
name: "alertmanager"
user: "postgres"
password: "password"
table_name: "alerts"
# Webhook 服务器配置
server:
host: "0.0.0.0"
port: 5000
debug: false
# 日志配置
logging:
level: "INFO"
2. 数据库初始化脚本 (init_db.sql)
-- 创建告警表
CREATE TABLE IF NOT EXISTS alerts (
id SERIAL PRIMARY KEY,
fingerprint VARCHAR(64) NOT NULL,
alertname VARCHAR(256) NOT NULL,
instance VARCHAR(256),
severity VARCHAR(64),
status VARCHAR(32) NOT NULL,
summary TEXT,
description TEXT,
starts_at TIMESTAMP NOT NULL,
ends_at TIMESTAMP,
generator_url TEXT,
labels JSONB,
annotations JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-- 唯一约束,确保 fingerprint + status 组合唯一
UNIQUE(fingerprint, status)
);
3. Python Webhook 程序 (webhook_app.py)
#!/usr/bin/env python3
import logging
import psycopg2
import yaml
from flask import Flask, request, jsonify
from datetime import datetime
from contextlib import contextmanager
import sys
import os
# 设置日志格式
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stdout)
]
)
logger = logging.getLogger(__name__)
def load_config():
"""加载配置文件"""
try:
config_path = os.getenv('CONFIG_PATH', 'config.yaml')
logger.info(f"Loading configuration from: {config_path}")
with open(config_path, 'r') as f:
config = yaml.safe_load(f)
logger.info("Configuration loaded successfully")
return config
except Exception as e:
logger.error(f"Failed to load configuration: {e}")
sys.exit(1)
# 加载配置
config = load_config()
app = Flask(__name__)
@contextmanager
def get_db_connection():
"""数据库连接上下文管理器"""
conn = None
db_config = config['database']
logger.debug(f"Attempting to connect to database: {db_config['host']}:{db_config['port']}")
try:
conn = psycopg2.connect(
host=db_config['host'],
port=db_config['port'],
dbname=db_config['name'],
user=db_config['user'],
password=db_config['password']
)
logger.debug("Database connection established successfully")
yield conn
except Exception as e:
logger.error(f"Database connection failed: {e}")
raise
finally:
if conn:
conn.close()
logger.debug("Database connection closed")
def init_database():
"""初始化数据库表"""
logger.info("Starting database initialization")
try:
with get_db_connection() as conn:
with conn.cursor() as cur:
logger.info("Creating database table from init_db.sql")
with open('init_db.sql', 'r') as f:
sql_content = f.read()
logger.debug(f"Executing SQL: {sql_content}")
cur.execute(sql_content)
conn.commit()
logger.info("Database table created successfully")
except Exception as e:
logger.error(f"Database initialization failed: {e}")
raise
def save_alert_to_db(alert_data):
"""保存告警到数据库,依赖数据库唯一约束去重"""
insert_sql = f"""
INSERT INTO {config['database']['table_name']} (
fingerprint, alertname, instance, severity, status, summary,
description, starts_at, ends_at, generator_url, labels, annotations
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
"""
logger.debug(f"Preparing to save alert: {alert_data['alertname']} with fingerprint: {alert_data['fingerprint']}")
try:
with get_db_connection() as conn:
with conn.cursor() as cur:
logger.debug(f"Executing SQL insert for alert: {alert_data['alertname']}")
cur.execute(insert_sql, (
alert_data['fingerprint'],
alert_data['alertname'],
alert_data.get('instance'),
alert_data.get('severity'),
alert_data['status'],
alert_data.get('summary'),
alert_data.get('description'),
alert_data['starts_at'],
alert_data.get('ends_at'),
alert_data.get('generator_url'),
alert_data.get('labels', {}),
alert_data.get('annotations', {})
))
conn.commit()
logger.info(f"Alert saved successfully: {alert_data['alertname']} - {alert_data['status']} - {alert_data['fingerprint']}")
return True
except psycopg2.IntegrityError:
logger.info(f"Alert already exists, skipping insert: {alert_data['fingerprint']} - {alert_data['status']}")
return False
except Exception as e:
logger.error(f"Failed to save alert {alert_data['alertname']}: {e}")
return False
def parse_alert_data(alert):
"""解析告警数据"""
logger.debug(f"Parsing alert data: {alert.get('fingerprint', 'unknown')}")
labels = alert.get('labels', {})
annotations = alert.get('annotations', {})
alert_data = {
'fingerprint': alert.get('fingerprint', ''),
'alertname': labels.get('alertname', 'unknown'),
'instance': labels.get('instance'),
'severity': labels.get('severity'),
'status': alert.get('status', 'unknown'),
'summary': annotations.get('summary'),
'description': annotations.get('description'),
'starts_at': datetime.fromisoformat(alert['startsAt'].replace('Z', '+00:00')),
'ends_at': datetime.fromisoformat(alert['endsAt'].replace('Z', '+00:00')) if alert.get('endsAt') else None,
'generator_url': alert.get('generatorURL'),
'labels': labels,
'annotations': annotations
}
logger.debug(f"Parsed alert: {alert_data['alertname']} - {alert_data['status']}")
return alert_data
@app.route('/webhook', methods=['POST'])
def handle_webhook():
"""处理 Alertmanager Webhook 请求"""
logger.info("Received webhook request")
try:
data = request.get_json()
if not data:
logger.warning("Webhook request contains no JSON data")
return jsonify({"error": "No JSON data received"}), 400
alerts = data.get('alerts', [])
logger.info(f"Processing {len(alerts)} alerts from webhook")
saved_count = 0
for i, alert in enumerate(alerts):
logger.debug(f"Processing alert {i+1}/{len(alerts)}")
alert_data = parse_alert_data(alert)
if save_alert_to_db(alert_data):
saved_count += 1
logger.debug(f"Alert {i+1} saved successfully")
else:
logger.debug(f"Alert {i+1} was duplicate or failed to save")
logger.info(f"Webhook processing completed: {saved_count}/{len(alerts)} alerts saved")
return jsonify({"status": "success", "saved": saved_count}), 200
except Exception as e:
logger.error(f"Webhook processing error: {e}", exc_info=True)
return jsonify({"error": "Internal server error"}), 500
@app.route('/health', methods=['GET'])
def health_check():
"""健康检查端点"""
logger.debug("Health check requested")
try:
with get_db_connection() as conn:
with conn.cursor() as cur:
cur.execute("SELECT 1")
logger.debug("Health check passed")
return jsonify({"status": "healthy"}), 200
except Exception as e:
logger.error(f"Health check failed: {e}")
return jsonify({"status": "unhealthy"}), 503
@app.route('/ready', methods=['GET'])
def ready_check():
"""就绪检查端点"""
logger.debug("Ready check requested")
try:
with get_db_connection() as conn:
with conn.cursor() as cur:
cur.execute(f"SELECT COUNT(*) FROM {config['database']['table_name']}")
count = cur.fetchone()[0]
logger.debug(f"Ready check passed, table contains {count} alerts")
return jsonify({"status": "ready", "alert_count": count}), 200
except Exception as e:
logger.error(f"Ready check failed: {e}")
return jsonify({"status": "not ready"}), 503
if __name__ == '__main__':
logger.info("Starting Alertmanager Webhook Service")
# 初始化数据库
try:
init_database()
except Exception as e:
logger.error(f"Failed to initialize database: {e}")
sys.exit(1)
# 启动 Webhook 服务器
server_config = config['server']
logger.info(f"Starting webhook server on {server_config['host']}:{server_config['port']}")
app.run(
host=server_config['host'],
port=server_config['port'],
debug=server_config['debug']
)
4. Dockerfile
FROM python:3.9-slim
WORKDIR /app
# 安装系统依赖
RUN apt-get update && apt-get install -y \
postgresql-client \
&& rm -rf /var/lib/apt/lists/*
# 复制应用文件
COPY webhook_app.py .
COPY config.yaml .
COPY init_db.sql .
# 安装 Python 依赖
RUN pip install --no-cache-dir \
flask==2.3.3 \
psycopg2-binary==2.9.7 \
pyyaml==6.0.1
# 创建非root用户
RUN useradd -m -u 1000 webhookuser && chown -R webhookuser:webhookuser /app
USER webhookuser
# 暴露端口
EXPOSE 5000
# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:5000/health || exit 1
# 启动命令
CMD ["python", "webhook_app.py"]
5. Docker Compose 文件 (docker-compose.yml)
version: '3.8'
services:
webhook:
build: .
ports:
- "5000:5000"
environment:
- CONFIG_PATH=/app/config.yaml
depends_on:
- postgres
restart: unless-stopped
postgres:
image: postgres:13
environment:
- POSTGRES_DB=alertmanager
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
volumes:
postgres_data:
6. 构建和运行脚本 (build_and_run.sh)
#!/bin/bash
# 构建镜像
echo "Building Docker image..."
docker build -t alertmanager-webhook .
# 运行服务
echo "Starting services..."
docker-compose up -d
# 检查服务状态
echo "Checking services..."
docker-compose ps
# 显示日志
echo "Showing logs..."
docker-compose logs -f webhook
7. 测试脚本 (test_webhook.py)
#!/usr/bin/env python3
import requests
import json
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def test_webhook():
url = "http://localhost:5000/webhook"
test_data = {
"receiver": "webhook",
"status": "firing",
"alerts": [
{
"status": "firing",
"labels": {
"alertname": "HighMemory",
"instance": "server1",
"severity": "warning"
},
"annotations": {
"summary": "High memory usage",
"description": "Memory usage is above 80%"
},
"startsAt": "2023-10-01T10:00:00Z",
"endsAt": "0001-01-01T00:00:00Z",
"generatorURL": "http://prometheus:9090",
"fingerprint": "test_fingerprint_123"
}
]
}
logger.info("Sending test webhook request")
response = requests.post(url, json=test_data)
logger.info(f"Response Status: {response.status_code}")
logger.info(f"Response Body: {response.json()}")
if __name__ == "__main__":
test_webhook()
构建和运行:
# 方式1: 使用 Docker Compose (推荐)
docker-compose up -d
# 方式2: 手动构建和运行
docker build -t alertmanager-webhook .
docker run -p 5000:5000 alertmanager-webhook
# 方式3: 使用构建脚本
chmod +x build_and_run.sh
./build_and_run.sh
测试服务:
bash
# 健康检查
curl http://localhost:5000/health
# 就绪检查
curl http://localhost:5000/ready
# 测试 webhook
python test_webhook.py
总结
数字化转型背景下,容器技术已成为现代应用开发的核心标准。云原生作为方法论,通过弹性、可观测性和韧性设计,充分发挥云计算潜力。其技术栈覆盖应用定义、编排调度、服务网格、可观测性及CI/CD,而容器运行时是底层关键组件。生产环境中,K8s集群推荐containerd+CRI-O组合,平衡性能与生态;开发测试可采用nerdctl保持Docker兼容性,podman的安全性更高但是生态不成熟,部署服务会受到大量权限问题限制,谨慎考虑。
更多推荐



所有评论(0)