Tips
Go
(18条消息) Go语言自学系列 | golang包_COCOgsta的博客-CSDN博客
(18条消息) Go语言自学系列 | golang并发编程之channel的遍历_COCOgsta的博客-CSDN博客
(18条消息) Go语言自学系列 | golang并发编程之select switch_COCOgsta的博客-CSDN博客_golang select switch
(18条消息) Go语言自学系列 | golang并发编程之runtime包_COCOgsta的博客-CSDN博客_golang runtime包
(18条消息) Go语言自学系列 | golang接口值类型接收者和指针类型接收者_COCOgsta的博客-CSDN博客
(18条消息) Go语言自学系列 | golang并发编程之Timer_COCOgsta的博客-CSDN博客
(18条消息) Go语言自学系列 | golang方法_COCOgsta的博客-CSDN博客
(18条消息) Go语言自学系列 | golang并发编程之WaitGroup实现同步_COCOgsta的博客-CSDN博客
(18条消息) Go语言自学系列 | golang构造函数_COCOgsta的博客-CSDN博客_golang 构造函数
(18条消息) Go语言自学系列 | golang方法接收者类型_COCOgsta的博客-CSDN博客_golang 方法接收者
(18条消息) Go语言自学系列 | golang接口_COCOgsta的博客-CSDN博客
(18条消息) Go语言自学系列 | golang接口和类型的关系_COCOgsta的博客-CSDN博客
(18条消息) Go语言自学系列 | golang结构体_COCOgsta的博客-CSDN博客
(18条消息) Go语言自学系列 | golang结构体_COCOgsta的博客-CSDN博客
(18条消息) Go语言自学系列 | golang标准库os模块 - File文件读操作_COCOgsta的博客-CSDN博客_golang os.file
(18条消息) Go语言自学系列 | golang继承_COCOgsta的博客-CSDN博客_golang 继承
(18条消息) Go语言自学系列 | golang嵌套结构体_COCOgsta的博客-CSDN博客_golang 结构体嵌套
(18条消息) Go语言自学系列 | golang并发编程之Mutex互斥锁实现同步_COCOgsta的博客-CSDN博客
(18条消息) Go语言自学系列 | golang并发变成之通道channel_COCOgsta的博客-CSDN博客
(18条消息) Go语言自学系列 | golang并发编程之原子操作详解_COCOgsta的博客-CSDN博客_golang 原子操作
(18条消息) Go语言自学系列 | golang并发编程之原子变量的引入_COCOgsta的博客-CSDN博客_go 原子变量
(18条消息) Go语言自学系列 | golang并发编程之协程_COCOgsta的博客-CSDN博客_golang 协程 并发
(18条消息) Go语言自学系列 | golang接口嵌套_COCOgsta的博客-CSDN博客_golang 接口嵌套
(18条消息) Go语言自学系列 | golang包管理工具go module_COCOgsta的博客-CSDN博客_golang 包管理器
(18条消息) Go语言自学系列 | golang标准库os模块 - File文件写操作_COCOgsta的博客-CSDN博客_go os模块
(18条消息) Go语言自学系列 | golang结构体的初始化_COCOgsta的博客-CSDN博客_golang 结构体初始化
(18条消息) Go语言自学系列 | golang通过接口实现OCP设计原则_COCOgsta的博客-CSDN博客
(18条消息) Go语言自学系列 | golang标准库os包进程相关操作_COCOgsta的博客-CSDN博客_golang os包
(18条消息) Go语言自学系列 | golang标准库ioutil包_COCOgsta的博客-CSDN博客_golang ioutil
(18条消息) Go语言自学系列 | golang标准库os模块 - 文件目录相关_COCOgsta的博客-CSDN博客_go语言os库
Golang技术栈,Golang文章、教程、视频分享!
(18条消息) Go语言自学系列 | golang结构体指针_COCOgsta的博客-CSDN博客_golang 结构体指针
Ansible
太厉害了,终于有人能把Ansible讲的明明白白了,建议收藏_互联网老辛
ansible.cfg配置详解
Docker
Docker部署
linux安装docker和Docker Compose
linux 安装 docker
Docker中安装Docker遇到的问题处理
Docker常用命令
docker常用命令小结
docker 彻底卸载
Docker pull 时报错:Get https://registry-1.docker.io/v2/library/mysql: net/http: TLS handshake timeout
Docker 拉镜像无法访问 registry-x.docker.io 问题(Centos7)
docker 容器内没有权限
Linux中关闭selinux的方法是什么?
docker run 生成 docker-compose
Docker覆盖网络部署
docker pull后台拉取镜像
docker hub
Redis
Redis 集群别乱搭,这才是正确的姿势
linux_离线_redis安装
怎么实现Redis的高可用?(主从、哨兵、集群) - 雨点的名字 - 博客园
redis集群离线安装
always-show-logo yes
Redis集群搭建及原理
[ERR] Node 172.168.63.202:7001 is not empty. Either the nodealready knows other nodes (check with CLUSTER NODES) or contains some - 亲爱的不二999 - 博客园
Redis daemonize介绍
redis 下载地址
Redis的redis.conf配置注释详解(三) - 云+社区 - 腾讯云
Redis的redis.conf配置注释详解(一) - 云+社区 - 腾讯云
Redis的redis.conf配置注释详解(二) - 云+社区 - 腾讯云
Redis的redis.conf配置注释详解(四) - 云+社区 - 腾讯云
Linux
在终端连接ssh的断开关闭退出的方法
漏洞扫描 - 灰信网(软件开发博客聚合)
find 命令的参数详解
vim 编辑器搜索功能
非root安装rpm时,mockbuild does not exist
Using a SSH password instead of a key is not possible because Host Key checking
(9条消息) 安全扫描5353端口mDNS服务漏洞问题_NamiJava的博客-CSDN博客_5353端口
Linux中使用rpm命令安装rpm包
ssh-copy-id非22端口的使用方法
How To Resolve SSH Weak Key Exchange Algorithms on CentOS7 or RHEL7 - infotechys.com
Linux cp 命令
yum 下载全量依赖 rpm 包及离线安装(终极解决方案) - 叨叨软件测试 - 博客园
How To Resolve SSH Weak Key Exchange Algorithms on CentOS7 or RHEL7 - infotechys.com
RPM zlib 下载地址
运维架构网站
欢迎来到 Jinja2
/usr/local/bin/ss-server -uv -c /etc/shadowsocks-libev/config.json -f /var/run/s
ruby 安装Openssl 默认安装位置
Linux 常用命令学习 | 菜鸟教程
linux 重命名文件和文件夹
linux命令快速指南
ipvsadm
Linux 下查找日志中的关键字
Linux 切割大 log 日志
CentOS7 关于网络的设置
rsync 命令_Linux rsync 命令用法详解:远程数据同步工具
linux 可视化界面安装
[问题已处理]-执行yum卡住无响应
GCC/G++升级高版本
ELK
Docker部署ELK
ELK+kafka+filebeat+Prometheus+Grafana - SegmentFault 思否
(9条消息) Elasticsearch设置账号密码_huas_xq的博客-CSDN博客_elasticsearch设置密码
Elasticsearch 7.X 性能优化
Elasticsearch-滚动更新
Elasticsearch 的内存优化_大数据系统
Elasticsearch之yml配置文件
ES 索引为Yellow状态
Logstash:Grok filter 入门
logstash grok 多项匹配
Mysql
Mysql相关Tip
基于ShardingJDBC实现数据库读写分离 - 墨天轮
MySQL-MHA高可用方案
京东三面:我要查询千万级数据量的表,怎么操作?
OpenStack
(16条消息) openstack项目中遇到的各种问题总结 其二(云主机迁移、ceph及扩展分区)_weixin_34104341的博客-CSDN博客
OpenStack组件介绍
百度大佬OpenStack流程
openstack各组件介绍
OpenStack生产实际问题总结(一)
OpenStack Train版离线部署
使用Packstack搭建OpenStack
K8S
K8S部署
K8S 集群部署
kubeadm 重新 init 和 join-pudn.com
Kubernetes 实战总结 - 阿里云 ECS 自建 K8S 集群 Kubernetes 实战总结 - 自定义 Prometheus
【K8S实战系列-清理篇1】k8s docker 删除没用的资源
Flannel Pod Bug汇总
Java
Jdk 部署
JDK部署
java线程池ThreadPoolExecutor类使用详解 - bigfan - 博客园
ShardingJDBC实现多数据库节点分库分表 - 墨天轮
Maven Repository: Search/Browse/Explore
其他
Git在阿里,我们如何管理代码分支?
chrome F12调试网页出现Paused in debugger
体验IntelliJ IDEA的远程开发(Remote Development) - 掘金
Idea远程调试
PDF转MD
强哥分享干货
优秀开源项目集合
vercel 配合Github 搭建项目Doc门户
如何用 Github Issues 写技术博客?
Idea 2021.3 Maven 3.8.1 报错 Blocked mirror for repositories 解决
列出maven依赖
[2022-09 持续更新] 谷歌 google 镜像 / Sci-Hub 可用网址 / Github 镜像可用网址总结
阿里云ECS迁移
linux访问github
一文教你使用 Docker 启动并安装 Nacos-腾讯云开发者社区-腾讯云
Nginx
Nginx 部署
Nginx 部署安装
Nginx反向代理cookie丢失的问题_longzhoufeng的博客-CSDN博客_nginx 代理后cookie丢失
Linux 系统 Https 证书生成与Nginx配置 https
数据仓库
实时数仓
松果出行 x StarRocks:实时数仓新范式的实践之路
实时数据仓库的一些分层和分层需要处理的事情,以及数据流向
湖仓一体电商项目
湖仓一体电商项目(一):项目背景和架构介绍
湖仓一体电商项目(二):项目使用技术及版本和基础环境准备
湖仓一体电商项目(三):3万字带你从头开始搭建12个大数据项目基础组件
数仓笔记
数仓学习总结
数仓常用平台和框架
数仓学习笔记
数仓技术选型
尚硅谷教程
尚硅谷学习笔记
尚硅谷所有已知的课件资料
尚硅谷大数据项目之尚品汇(11数据质量管理V4.0)
尚硅谷大数据项目之尚品汇(10元数据管理AtlasV4.0)
尚硅谷大数据项目之尚品汇(9权限管理RangerV4.0)
尚硅谷大数据项目之尚品汇(8安全环境实战V4.0)
尚硅谷大数据项目之尚品汇(7用户认证KerberosV4.1)
尚硅谷大数据项目之尚品汇(6集群监控ZabbixV4.1)
尚硅谷大数据项目之尚品汇(5即席查询PrestoKylinV4.0)
尚硅谷大数据项目之尚品汇(4可视化报表SupersetV4.0)
尚硅谷大数据项目之尚品汇(3数据仓库系统)V4.2.0
尚硅谷大数据项目之尚品汇(2业务数据采集平台)V4.1.0
尚硅谷大数据项目之尚品汇(1用户行为采集平台)V4.1.0
数仓治理
数据中台 元数据规范
数据中台的那些 “经验与陷阱”
2万字详解数据仓库数据指标数据治理体系建设方法论
数据仓库,为什么需要分层建设和管理? | 人人都是产品经理
网易数帆数据治理演进
数仓技术
一文看懂大数据生态圈完整知识体系
阿里云—升舱 - 数据仓库升级白皮书
最全企业级数仓建设迭代版(4W字建议收藏)
基于Hue,Dolphinscheduler,HIVE分析数据仓库层级实现及项目需求案例实践分析
详解数据仓库分层架构
数据仓库技术细节
大数据平台组件介绍
总览 2016-2021 年全球机器学习、人工智能和大数据行业技术地图
Apache DolphinScheduler 3.0.0 正式版发布!
数据仓库面试题——介绍下数据仓库
数据仓库为什么要分层,各层的作用是什么
Databend v0.8 发布,基于 Rust 开发的现代化云数据仓库 - OSCHINA - 中文开源技术交流社区
数据中台
数据中台设计
大数据同步工具之 FlinkCDC/Canal/Debezium 对比
有数数据开发平台文档
Shell
Linux Shell 命令参数
shell 脚本编程
一篇教会你写 90% 的 Shell 脚本
Kibana
Kibana 查询语言(KQL)
Kibana:在 Kibana 中的四种表格制作方式
Kafka
Kafka部署
canal 动态监控 Mysql,将 binlog 日志解析后,把采集到的数据发送到 Kafka
OpenApi
OpenAPI 标准规范,了解一下?
OpenApi学术论文
贵阳市政府数据开放平台设计与实现
OpenAPI简介
开放平台:运营模式与技术架构研究综述
管理
技术部门Leader是不是一定要技术大牛担任?
华为管理体系流程介绍
DevOps
*Ops
XOps 已经成为一个流行的术语 - 它是什么?
Practical Linux DevOps
Jenkins 2.x实践指南 (翟志军)
Jenkins 2权威指南 ((美)布伦特·莱斯特(Brent Laster)
DevOps组件高可用的思路
KeepAlived
VIP + KEEPALIVED + LVS 遇到Connection Peer的问题的解决
MinIO
MinIO部署
Minio 分布式集群搭建部署
Minio 入门系列【16】Minio 分片上传文件 putObject 接口流程源码分析
MinioAPI 浅入及问题
部署 minio 兼容 aws S3 模式
超详细分布式对象存储 MinIO 实战教程
Hadoop
Hadoop 部署
Hadoop集群部署
windows 搭建 hadoop 环境(解决 HADOOP_HOME and hadoop.home.dir are unset
Hadoop 集群搭建和简单应用(参考下文)
Hadoop 启动 NameNode 报错 ERROR: Cannot set priority of namenode process 2639
jps 命令查看 DataNode 进程不见了 (hadoop3.0 亲测可用)
hadoop 报错: Operation category READ is not supported in state standby
Spark
Spark 部署
Spark 集群部署
spark 心跳超时分析 Cannot receive any reply in 120 seconds
Spark学习笔记
apache spark - Failed to find data source: parquet, when building with sbt assembly
Spark Thrift Server 架构和原理介绍
InLong
InLong 部署
Apache InLong部署文档
安装部署 - Docker 部署 - 《Apache InLong v1.2 中文文档》 - 书栈网 · BookStack
基于 Apache Flink SQL 的 InLong Sort ETL 方案解析
关于 Apache Pulsar 在 Apache InLong 接入数据
zookeeper
zookeeper 部署
使用 Docker 搭建 Zookeeper 集群
美团技术团队
StarRocks
StarRocks技术白皮书(在线版)
JuiceFS
AI 场景存储优化:云知声超算平台基于 JuiceFS 的存储实践
JuiceFS 在 Elasticsearch/ClickHouse 温冷数据存储中的实践
JuiceFS format
元数据备份和恢复 | JuiceFS Document Center
JuiceFS 元数据引擎选型指南
Apache Hudi 使用文件聚类功能 (Clustering) 解决小文件过多的问题
普罗米修斯
k8s 之 Prometheus(普罗米修斯)监控,简单梳理下 K8S 监控流程
k8s 部署 - 使用helm3部署监控prometheus(普罗米修斯),从零到有,一文搞定
k8s 部署 - 使用 helm3 部署监控 prometheus(普罗米修斯),从零到有,一文搞定
k8s 部署 - 如何完善 k8s 中 Prometheus(普罗米修斯)监控项目呢?
k8s 部署 - k8s 中 Prometheus(普罗米修斯)的大屏展示 Grafana + 监控报警
zabbix
一文带你掌握 Zabbix 监控系统
Stream Collectors
Nvidia
Nvidia API
CUDA Nvidia驱动安装
NVIDIA驱动失效简单解决方案:NVIDIA-SMI has failed because it couldn‘t communicate with the NVIDIA driver.
ubuntu 20 CUDA12.1安装流程
nvidia开启持久化模式
nvidia-smi 开启持久化
Harbor
Harbor部署文档
Docker 爆出 it doesn't contain any IP SANs
pandoc
其他知识
大模型
COS 597G (Fall 2022): Understanding Large Language Models
如何优雅的使用各类LLM
ChatGLM3在线搜索功能升级
当ChatGLM3能用搜索引擎时
OCR神器,PDF、数学公式都能转
Stable Diffusion 动画animatediff-cli-prompt-travel
基于ERNIE Bot自定义虚拟数字人生成
pika负面提示词
开通GPT4的方式
GPT4网站
低价开通GPT Plus
大模型应用场景分享
AppAgent AutoGPT变体
机器学习
最大似然估计
权衡偏差(Bias)和方差(Variance)以最小化均方误差(Mean Squared Error, MSE)
伯努利分布
方差计算公式
均值的高斯分布估计
没有免费午餐定理
贝叶斯误差
非参数模型
最近邻回归
表示容量
最优容量
权重衰减
正则化项
Sora
Sora官方提示词
看完32篇论文,你大概就知道Sora如何炼成? |【经纬低调出品】
Sora论文
Sora 物理悖谬的几何解释
Sora 技术栈讨论
RAG垂直落地
DB-GPT与TeleChat-7B搭建相关RAG知识库
ChatWithRTX
ChatRTX安装教程
ChatWithRTX 踩坑记录
ChatWithRTX 使用其他量化模型
ChatWithRTX介绍
RAG 相关资料
英伟达—大模型结合 RAG 构建客服场景自动问答
又一大模型技术开源!有道自研RAG引擎QAnything正式开放下载
收藏!RAG入门参考资料开源大总结:RAG综述、介绍、比较、预处理、RAG Embedding等
RAG调研
解决现代RAG实际生产问题
解决现代 RAG 系统中的生产问题-II
Modular RAG and RAG Flow: Part Ⅰ
Modular RAG and RAG Flow: Part II
先进的Retriever技术来增强你的RAGs
高级RAG — 使用假设文档嵌入 (HyDE) 改进检索
提升 RAG:选择最佳嵌入和 Reranker 模型
LangGraph
增强型RAG:re-rank
LightRAG:使用 PyTorch 为 LLM 应用程序提供支持
RAG 101:分块策略
模型训练
GPU相关资料
[教程] conda安装简明教程(基于miniconda和Windows)
PyTorch CUDA对应版本 | PyTorch
资料
李一舟课程全集
零碎资料
苹果各服共享ID
数据中心网络技术概览
华为大模型训练学习笔记
百度AIGC工程师认证考试答案(可换取工信部证书)
百度智能云生成式AI认证工程师 考试和证书查询指南
深入理解 Megatron-LM(1)基础知识
QAnything
接入QAnything的AI问答知识库,可私有化部署的企业级WIKI知识库
wsl --update失效Error code: Wsl/UpdatePackage/0x80240438的解决办法
Docker Desktop 启动docker engine一直转圈解决方法
win10开启了hyper-v,docker 启动还是报错 docker desktop windows hypervisor is not present
WSL虚拟磁盘过大,ext4迁移 Windows 中创建软链接和硬链接
WSL2切换默认的Linux子系统
Windows的WSL子系统,自动开启sshd服务
新版docker desktop设置wsl(使用windown的子系统)
WSL 开启ssh
Windows安装网易开源QAnything打造智能客服系统
芯片
国内互联网大厂自研芯片梳理
超算平台—算力供应商
Linux 磁盘扩容
Linux使用growpart工具进行磁盘热扩容(非LVM扩容方式)
关于centos7 扩容提示no tools available to resize disk with 'gpt' - o夜雨随风o - 博客园
(小插曲)neo4j配置apoc插件后检查版本发现:Unknown function ‘apoc.version‘ “EXPLAIN RETURN apoc.version()“
vfio-pci与igb_uio映射硬件资源到DPDK的流程分析
KubeVirt
vnc server配置、启动、重启与连接 - 王约翰 - 博客园
虚拟机Bug解决方案
kubevirt 如何通过CDI上传镜像文件
在 K8S 上也能跑 VM!KubeVirt 簡介與建立(部署篇) | Cloud Solutions
KubeVirt 04:容器化数据导入 – 小菜园
Python
安装 flash_attn
手把手教你在linux上安装pytorch与cuda
AI
在启智社区基于PyTorch运行国产算力卡的模型训练实验
Scaling law
免费的GPT3.5 API
AI Engineer Roadmap & Resources 🤖
模型排行
edk2
K8S删除Evicted状态的pod
docker 中启动 docker
远程本地多用户桌面1.17(一种不让电脑跟你抢键鼠的思路) - 哔哩哔哩
华为鲲鹏服务器(ARM架构)部署Prometheus
在Linux上安装配置Grafana_AI开发平台ModelArts_华为云
abrt-ccpp干崩服务器查询记录
kubevirt 中文社区
VNCServer 连接方法
Pod创建流程代码版本[kubelet篇]
[译]深入剖析 Kubernetes MutatingAdmissionWebhook-腾讯云开发者社区-腾讯云
[译]深入剖析 Kubernetes MutatingAdmissionWebhook-腾讯云开发者社区-腾讯云
深入理解 Kubernetes Admission Webhook-阳明的博客
CentOS7 安装 mbedtls和mbedtls-devel
docker in docker 启动命令
go 协程泄漏 pprof
-
+
首页
深入理解 Kubernetes Admission Webhook-阳明的博客
[Kubernetes](https://www.qikqiak.com/tags/kubernetes/) 提供了需要扩展其内置功能的方法,最常用的可能是自定义资源类型和自定义控制器了,除此之外,[Kubernetes](https://www.qikqiak.com/tags/kubernetes/) 还有一些其他非常有趣的功能,比如 [admission webhooks](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#admission-webhooks) 或者 [initializers](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#initializers),这些也可以用于扩展 API,它们可以用于修改某些 Kubernetes 资源的基本行为,接下来我们来看看那些引入了 admission webhooks 的动态准入控制。 ## 准入控制器 首先,我们先看看 Kubernetes 官方文档中关于`准入控制器`的定义: > An admission controller is a piece of code that intercepts requests to the Kubernetes API server prior to persistence of the object, but after the request is authenticated and authorized. \[…\] Admission controllers may be “validating”, “mutating”, or both. Mutating controllers may modify the objects they admit; validating controllers may not. \[…\] If any of the controllers in either phase reject the request, the entire request is rejected immediately and an error is returned to the end-user. 大概意思就是说`准入控制器`是在对象持久化之前用于对 Kubernetes API Server 的请求进行拦截的代码段,在请求经过身份验证和授权之后放行通过。准入控制器可能正在`validating`、`mutating`或者都在执行,Mutating 控制器可以修改他们的处理的资源对象,Validating 控制器不会,如果任何一个阶段中的任何控制器拒绝了请求,则会立即拒绝整个请求,并将错误返回给最终的用户。 这意味着有一些特殊的控制器可以拦截 Kubernetes API 请求,并根据自定义的逻辑修改或者拒绝它们。Kubernetes 有自己实现的一个控制器列表:[https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#what-does-each-admission-controller-do](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#what-does-each-admission-controller-do),当然你也可以编写自己的控制器,虽然这些控制器听起来功能比较强大,但是这些控制器需要被编译进 kube-apiserver,并且只能在 apiserver 启动时启动。 由于上面的控制器的限制,我们就需要用到\*\*“动态”\*\*的概念了,而不是和 apiserver 耦合在一起,`Admission webhooks`和`initializers`就通过一种动态配置方法解决了这个限制问题。对于这两个功能,`initializers`属于比较新的功能,而且平时用得非常少,还是一个`alpha`特性,所以更多的我们会来了解下`Admission webhooks`的使用方法。 > 在新版本(1.14+) kubernetes 集群中已经移除了对`initializers`的支持,所以可以不用考虑这种方式。 ### admission webhook 是什么? 在 Kubernetes apiserver 中包含两个特殊的准入控制器:`MutatingAdmissionWebhook`和`ValidatingAdmissionWebhook`。这两个控制器将发送准入请求到外部的 HTTP 回调服务并接收一个准入响应。如果启用了这两个准入控制器,Kubernetes 管理员可以在集群中创建和配置一个 admission webhook。 ![k8s api request lifecycle](https://picdn.youdianzhishi.com/images/k8s-api-request-lifecycle.png) 总的来说,这样做的步骤如下: 这两种类型的 admission webhook 之间的区别是非常明显的:validating webhooks 可以拒绝请求,但是它们却不能修改在准入请求中获取的对象,而 mutating webhooks 可以在返回准入响应之前通过创建补丁来修改对象,如果 webhook 拒绝了一个请求,则会向最终用户返回错误。 现在非常火热的的 [Service Mesh](https://www.qikqiak.com/post/what-is-service-mesh/) 应用`istio`就是通过 mutating webhooks 来自动将`Envoy`这个 sidecar 容器注入到 Pod 中去的:[https://istio.io/docs/setup/kubernetes/sidecar-injection/](https://istio.io/docs/setup/kubernetes/sidecar-injection/)。 ## 创建配置一个 Admission Webhook 上面我们介绍了 Admission Webhook 的理论知识,接下来我们在一个真实的 Kubernetes 集群中来实际测试使用下,我们将创建一个 webhook 的 webserver,将其部署到集群中,然后创建 webhook 配置查看是否生效。 ### 先决条件 一个 Kubernetes 当然是必须的,你可以通过[二进制](https://www.qikqiak.com/post/manual-install-high-available-kubernetes-cluster/)或者 [Kubeadm 来快速搭建集群](https://www.qikqiak.com/post/use-kubeadm-install-kubernetes-1.10/),或者使用云服务厂商托管的集群都可以。(1.9 版本以上) 然后确保在 apiserver 中启用了`MutatingAdmissionWebhook`和`ValidatingAdmissionWebhook`这两个控制器,由于我这里集群使用的是 kubeadm 搭建的,可以通过查看 apiserver Pod 的配置: ```shell $ kubectl get pods kube-apiserver-ydzs-master -n kube-system -o yaml apiVersion: v1 kind: Pod metadata: labels: component: kube-apiserver tier: control-plane name: kube-apiserver-ydzs-master namespace: kube-system ...... spec: containers: - command: - kube-apiserver - --advertise-address=10.151.30.11 - --allow-privileged=true - --authorization-mode=Node,RBAC - --client-ca-file=/etc/kubernetes/pki/ca.crt - --enable-admission-plugins=NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook ...... ``` 上面的`enable-admission-plugins`参数中带上了`MutatingAdmissionWebhook`和`ValidatingAdmissionWebhook`两个准入控制插件,如果没有的,需要添加上这两个参数,然后重启 apiserver。 然后通过运行下面的命令检查集群中是否启用了准入注册 API: ```shell $ kubectl api-versions |grep admission admissionregistration.k8s.io/v1beta1 ``` ### 编写 webhook 满足了前面的先决条件后,接下来我们就来实现一个 webhook 示例,通过监听两个不同的 HTTP 路径(validate 和 mutate)来进行 validating 和 mutating webhook 验证。 这个 webhook 的完整代码可以在 Github 上获取:[https://github.com/cnych/admission-webhook-example](https://github.com/cnych/admission-webhook-example),该代码 Fork 自仓库[https://github.com/banzaicloud/admission-webhook-example](https://github.com/banzaicloud/admission-webhook-example)。这个 webhook 是一个简单的带 TLS 认证的 HTTP 服务,用 Deployment 方式部署在我们的集群中。 代码中主要的逻辑在两个文件中:`main.go`和`webhook.go`,`main.go`文件包含创建 HTTP 服务的代码,而`webhook.go`包含 validates 和 mutates 两个 webhook 的逻辑,大部分代码都比较简单,首先查看`main.go`文件,查看如何使用标准 golang 包来启动 HTTP 服务,以及如何从命令行标志中[读取 TLS 配置](https://github.com/cnych/admission-webhook-example/blob/blog/main.go#L21)的证书: ```golang flag.StringVar(¶meters.certFile, "tlsCertFile", "/etc/webhook/certs/cert.pem", "File containing the x509 Certificate for HTTPS.") flag.StringVar(¶meters.keyFile, "tlsKeyFile", "/etc/webhook/certs/key.pem", "File containing the x509 private key to --tlsCertFile.") ``` 然后一个比较重要的是 serve 函数,用来处理传入的 mutate 和 validating 函数 的 HTTP 请求。该函数从请求中反序列化 AdmissionReview 对象,执行一些基本的内容校验,根据 URL 路径调用相应的 mutate 和 validate 函数,然后序列化 AdmissionReview 对象: ```golang func (whsvr *WebhookServer) serve(w http.ResponseWriter, r *http.Request) { var body []byte if r.Body != nil { if data, err := ioutil.ReadAll(r.Body); err == nil { body = data } } if len(body) == 0 { glog.Error("empty body") http.Error(w, "empty body", http.StatusBadRequest) return } // verify the content type is accurate contentType := r.Header.Get("Content-Type") if contentType != "application/json" { glog.Errorf("Content-Type=%s, expect application/json", contentType) http.Error(w, "invalid Content-Type, expect `application/json`", http.StatusUnsupportedMediaType) return } var admissionResponse *v1beta1.AdmissionResponse ar := v1beta1.AdmissionReview{} if _, _, err := deserializer.Decode(body, nil, &ar); err != nil { glog.Errorf("Can't decode body: %v", err) admissionResponse = &v1beta1.AdmissionResponse{ Result: &metav1.Status{ Message: err.Error(), }, } } else { fmt.Println(r.URL.Path) if r.URL.Path == "/mutate" { admissionResponse = whsvr.mutate(&ar) } else if r.URL.Path == "/validate" { admissionResponse = whsvr.validate(&ar) } } admissionReview := v1beta1.AdmissionReview{} if admissionResponse != nil { admissionReview.Response = admissionResponse if ar.Request != nil { admissionReview.Response.UID = ar.Request.UID } } resp, err := json.Marshal(admissionReview) if err != nil { glog.Errorf("Can't encode response: %v", err) http.Error(w, fmt.Sprintf("could not encode response: %v", err), http.StatusInternalServerError) } glog.Infof("Ready to write reponse ...") if _, err := w.Write(resp); err != nil { glog.Errorf("Can't write response: %v", err) http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError) } } ``` 主要的准入逻辑是 validate 和 mutate 两个函数。validate 函数检查资源对象是否需要校验:不验证 kube-system 和 kube-public 两个命名空间中的资源,如果想要显示的声明不验证某个资源,可以通过在资源对象中添加一个`admission-webhook-example.qikqiak.com/validate=false`的 annotation 进行声明。如果需要验证,则根据资源类型的 kind,和标签与其对应项进行比较,将 service 或者 deployment 资源从请求中反序列化出来。如果缺少某些 label 标签,则响应中的`Allowed`会被设置为 false。如果验证失败,则会在响应中写入失败原因,最终用户在尝试创建资源时会收到失败的信息。validate 函数实现如下所示: ```golang // validate deployments and services func (whsvr *WebhookServer) validate(ar *v1beta1.AdmissionReview) *v1beta1.AdmissionResponse { req := ar.Request var ( availableLabels map[string]string objectMeta *metav1.ObjectMeta resourceNamespace, resourceName string ) glog.Infof("AdmissionReview for Kind=%v, Namespace=%v Name=%v (%v) UID=%v patchOperation=%v UserInfo=%v", req.Kind, req.Namespace, req.Name, resourceName, req.UID, req.Operation, req.UserInfo) switch req.Kind.Kind { case "Deployment": var deployment appsv1.Deployment if err := json.Unmarshal(req.Object.Raw, &deployment); err != nil { glog.Errorf("Could not unmarshal raw object: %v", err) return &v1beta1.AdmissionResponse{ Result: &metav1.Status{ Message: err.Error(), }, } } resourceName, resourceNamespace, objectMeta = deployment.Name, deployment.Namespace, &deployment.ObjectMeta availableLabels = deployment.Labels case "Service": var service corev1.Service if err := json.Unmarshal(req.Object.Raw, &service); err != nil { glog.Errorf("Could not unmarshal raw object: %v", err) return &v1beta1.AdmissionResponse{ Result: &metav1.Status{ Message: err.Error(), }, } } resourceName, resourceNamespace, objectMeta = service.Name, service.Namespace, &service.ObjectMeta availableLabels = service.Labels } if !validationRequired(ignoredNamespaces, objectMeta) { glog.Infof("Skipping validation for %s/%s due to policy check", resourceNamespace, resourceName) return &v1beta1.AdmissionResponse{ Allowed: true, } } allowed := true var result *metav1.Status glog.Info("available labels:", availableLabels) glog.Info("required labels", requiredLabels) for _, rl := range requiredLabels { if _, ok := availableLabels[rl]; !ok { allowed = false result = &metav1.Status{ Reason: "required labels are not set", } break } } return &v1beta1.AdmissionResponse{ Allowed: allowed, Result: result, } } ``` 判断是否需要进行校验的方法如下,可以通过 namespace 进行忽略,也可以通过 annotations 设置进行配置: ```golang func validationRequired(ignoredList []string, metadata *metav1.ObjectMeta) bool { required := admissionRequired(ignoredList, admissionWebhookAnnotationValidateKey, metadata) glog.Infof("Validation policy for %v/%v: required:%v", metadata.Namespace, metadata.Name, required) return required } func admissionRequired(ignoredList []string, admissionAnnotationKey string, metadata *metav1.ObjectMeta) bool { // skip special kubernetes system namespaces for _, namespace := range ignoredList { if metadata.Namespace == namespace { glog.Infof("Skip validation for %v for it's in special namespace:%v", metadata.Name, metadata.Namespace) return false } } annotations := metadata.GetAnnotations() if annotations == nil { annotations = map[string]string{} } var required bool switch strings.ToLower(annotations[admissionAnnotationKey]) { default: required = true case "n", "no", "false", "off": required = false } return required } ``` mutate 函数的代码是非常类似的,但不是仅仅比较标签并在响应中设置`Allowed`,而是创建一个补丁,将缺失的标签添加到资源中,并将`not_available`设置为标签的值。 ```golang // main mutation process func (whsvr *WebhookServer) mutate(ar *v1beta1.AdmissionReview) *v1beta1.AdmissionResponse { req := ar.Request var ( availableLabels, availableAnnotations map[string]string objectMeta *metav1.ObjectMeta resourceNamespace, resourceName string ) glog.Infof("AdmissionReview for Kind=%v, Namespace=%v Name=%v (%v) UID=%v patchOperation=%v UserInfo=%v", req.Kind, req.Namespace, req.Name, resourceName, req.UID, req.Operation, req.UserInfo) switch req.Kind.Kind { case "Deployment": var deployment appsv1.Deployment if err := json.Unmarshal(req.Object.Raw, &deployment); err != nil { glog.Errorf("Could not unmarshal raw object: %v", err) return &v1beta1.AdmissionResponse{ Result: &metav1.Status{ Message: err.Error(), }, } } resourceName, resourceNamespace, objectMeta = deployment.Name, deployment.Namespace, &deployment.ObjectMeta availableLabels = deployment.Labels case "Service": var service corev1.Service if err := json.Unmarshal(req.Object.Raw, &service); err != nil { glog.Errorf("Could not unmarshal raw object: %v", err) return &v1beta1.AdmissionResponse{ Result: &metav1.Status{ Message: err.Error(), }, } } resourceName, resourceNamespace, objectMeta = service.Name, service.Namespace, &service.ObjectMeta availableLabels = service.Labels } if !mutationRequired(ignoredNamespaces, objectMeta) { glog.Infof("Skipping validation for %s/%s due to policy check", resourceNamespace, resourceName) return &v1beta1.AdmissionResponse{ Allowed: true, } } annotations := map[string]string{admissionWebhookAnnotationStatusKey: "mutated"} patchBytes, err := createPatch(availableAnnotations, annotations, availableLabels, addLabels) if err != nil { return &v1beta1.AdmissionResponse{ Result: &metav1.Status{ Message: err.Error(), }, } } glog.Infof("AdmissionResponse: patch=%v\n", string(patchBytes)) return &v1beta1.AdmissionResponse{ Allowed: true, Patch: patchBytes, PatchType: func() *v1beta1.PatchType { pt := v1beta1.PatchTypeJSONPatch return &pt }(), } } ``` ### 构建 其实我们已经将代码打包成一个 docker 镜像了,你可以直接使用,镜像仓库地址为:`cnych/admission-webhook-example:v1`。当然如果你希望更改部分代码,那就需要重新构建项目了,由于这个项目采用 go 语言开发,依赖使用的是`dep`工具,所以我们需要确保构建环境提前安装好 go 环境和 dep 工具,当然 docker 也是必不可少的,因为我们需要的是打包成一个 docker 镜像。 新建项目文件夹: ```shell $ mkdir admission-webhook && cd admission-webhook # 创建go项目代码目录,设置当前目录为GOPATH路径 $ mkdir src && export GOPATH=$pwd $ mkdir -p src/github.com/cnych/ && cd src/github.com/cnych/ ``` 进入到上面的`src/github.com/cnych/`目录下面,将项目代码 clone 下面: ```shell $ git clone https://github.com/cnych/admission-webhook-example.git ``` 我们可以看到代码根目录下面有一个`build`的脚本,只需要提供我们自己的 docker 镜像用户名然后直接构建即可: ```shell $ export DOCKER_USER=cnych $ ./build ``` ### 部署服务 为了部署 webhook server,我们需要在我们的 Kubernetes 集群中创建一个 service 和 deployment 资源对象,部署是非常简单的,只是需要配置下服务的 TLS 配置。我们可以在代码根目录下面的 deployment 文件夹下面查看`deployment.yaml`文件中关于证书的配置声明,会发现从命令行参数中读取的证书和私钥文件是通过一个 secret 对象挂载进来的: ```yaml args: - -tlsCertFile=/etc/webhook/certs/cert.pem - -tlsKeyFile=/etc/webhook/certs/key.pem [...] volumeMounts: - name: webhook-certs mountPath: /etc/webhook/certs readOnly: true volumes: - name: webhook-certs secret: secretName: admission-webhook-example-certs ``` 在生产环境中,对于 TLS 证书(特别是私钥)的处理是非常重要的,我们可以使用类似于[cert-manager](https://www.qikqiak.com/post/automatic-kubernetes-ingress-https-with-lets-encrypt)之类的工具来自动处理 TLS 证书,或者将私钥密钥存储在 Vault 中,而不是直接存在 secret 资源对象中。 我们可以使用任何类型的证书,但是需要注意的是我们这里设置的 CA 证书是需要让 apiserver 能够验证的,我们这里可以重用 Istio 项目中的生成的[证书签名请求脚本](https://github.com/istio/istio/blob/release-0.7/install/kubernetes/webhook-create-signed-cert.sh)。通过发送请求到 apiserver,获取认证信息,然后使用获得的结果来创建需要的 secret 对象。 首先,运行[该脚本](https://github.com/cnych/admission-webhook-example/blob/blog/deployment/webhook-create-signed-cert.sh)检查 secret 对象中是否有证书和私钥信息: ```shell $ ./deployment/webhook-create-signed-cert.sh creating certs in tmpdir /var/folders/x3/wjy_1z155pdf8jg_jgpmf6kc0000gn/T/tmp.IboFfX97 Generating RSA private key, 2048 bit long modulus (2 primes) ..................+++++ ........+++++ e is 65537 (0x010001) certificatesigningrequest.certificates.k8s.io/admission-webhook-example-svc.default created NAME AGE REQUESTOR CONDITION admission-webhook-example-svc.default 1s kubernetes-admin Pending certificatesigningrequest.certificates.k8s.io/admission-webhook-example-svc.default approved secret/admission-webhook-example-certs created $ kubectl get secret admission-webhook-example-certs NAME TYPE DATA AGE admission-webhook-example-certs Opaque 2 28s ``` 一旦 secret 对象创建成功,我们就可以直接创建 deployment 和 service 对象。 ```shell $ kubectl create -f deployment/deployment.yaml deployment.apps "admission-webhook-example-deployment" created $ kubectl create -f deployment/service.yaml service "admission-webhook-example-svc" created ``` ### 配置 webhook 现在我们的 webhook 服务运行起来了,它可以接收来自 apiserver 的请求。但是我们还需要在 kubernetes 上创建一些配置资源。首先来配置 validating 这个 webhook,查看 [webhook 配置](https://github.com/cnych/admission-webhook-example/blob/blog/deployment/validatingwebhook.yaml),我们会注意到它里面包含一个`CA_BUNDLE`的占位符: ```yaml clientConfig: service: name: admission-webhook-example-svc namespace: default path: "/validate" caBundle: ${CA_BUNDLE} ``` CA 证书应提供给 admission webhook 配置,这样 apiserver 才可以信任 webhook server 提供的 TLS 证书。因为我们上面已经使用 Kubernetes API 签署了证书,所以我们可以使用我们的 kubeconfig 中的 CA 证书来简化操作。代码仓库中也提供了一个小脚本用来替换 CA\_BUNDLE 这个占位符,创建 validating webhook 之前运行该命令即可: ```shell $ cat ./deployment/validatingwebhook.yaml | ./deployment/webhook-patch-ca-bundle.sh > ./deployment/validatingwebhook-ca-bundle.yaml ``` 执行完成后可以查看`validatingwebhook-ca-bundle.yaml`文件中的`CA_BUNDLE`占位符的值是否已经被替换掉了。需要注意的是 clientConfig 里面的 path 路径是`/validate`,因为我们代码在是将 validate 和 mutate 集成在一个服务中的。 然后就是需要配置一些 RBAC 规则,我们想在 deployment 或 service 创建时拦截 API 请求,所以`apiGroups`和`apiVersions`对应的值分别为`apps/v1`对应 deployment,`v1`对应 service。对于 RBAC 的配置方法可以查看我们前面的文章:[Kubernetes RBAC 详解](https://www.qikqiak.com/post/use-rbac-in-k8s) webhook 的最后一部分是配置一个`namespaceSelector`,我们可以为 webhook 工作的命名空间定义一个 selector,这个配置不是必须的,比如我们这里添加了下面的配置: ```yaml namespaceSelector: matchLabels: admission-webhook-example: enabled ``` 则我们的 webhook 会只适用于设置了`admission-webhook-example=enabled`标签的 namespace, 您可以在 Kubernetes 参考文档中查看此资源配置的完整布局。 所以,首先需要在`default`这个 namespace 中添加该标签: ```shell $ kubectl label namespace default admission-webhook-example=enabled namespace "default" labeled ``` 最后,创建这个 validating webhook 配置对象,这会动态地将 webhook 添加到 webhook 链上,所以一旦创建资源,就会拦截请求然后调用我们的 webhook 服务: ```shell $ kubectl create -f deployment/validatingwebhook-ca-bundle.yaml validatingwebhookconfiguration.admissionregistration.k8s.io "validation-webhook-example-cfg" created ``` ### 测试 现在让我们创建一个 deployment 资源来验证下是否有效,代码仓库下有一个`sleep.yaml`的资源清单文件,直接创建即可: ```shell $ kubectl create -f deployment/sleep.yaml Error from server (required labels are not set): error when creating "deployment/sleep.yaml": admission webhook "required-labels.qikqiak.com" denied the request: required labels are not set ``` 正常情况下创建的时候会出现上面的错误信息,然后部署另外一个`sleep-with-labels.yaml`的资源清单: ```shell $ kubectl create -f deployment/sleep-with-labels.yaml deployment.apps "sleep" created ``` 可以看到可以正常部署,先我们将上面的 deployment 删除,然后部署另外一个`sleep-no-validation.yaml`资源清单,该清单中不存在所需的标签,但是配置了`admission-webhook-example.qikqiak.com/validate=false`这样的 annotation,正常也是可以正常创建的: ```shell $ kubectl delete deployment sleep $ kubectl create -f deployment/sleep-no-validation.yaml deployment.apps "sleep" created ``` ### 部署 mutating webhook 首先,我们将上面的 validating webhook 删除,防止对 mutating 产生干扰,然后部署新的配置。 mutating webhook 与 validating webhook 配置基本相同,但是 webook server 的路径是`/mutate`,同样的我们也需要先填充上`CA_BUNDLE`这个占位符。 ```shell $ kubectl delete validatingwebhookconfiguration validation-webhook-example-cfg validatingwebhookconfiguration.admissionregistration.k8s.io "validation-webhook-example-cfg" deleted $ cat ./deployment/mutatingwebhook.yaml | ./deployment/webhook-patch-ca-bundle.sh > ./deployment/mutatingwebhook-ca-bundle.yaml $ kubectl create -f deployment/mutatingwebhook-ca-bundle.yaml mutatingwebhookconfiguration.admissionregistration.k8s.io "mutating-webhook-example-cfg" created ``` 现在我们可以再次部署上面的`sleep`应用程序,然后查看是否正确添加 label 标签: ```shell $ kubectl create -f deployment/sleep.yaml deployment.apps "sleep" created $ kubectl get deploy sleep -o yaml apiVersion: extensions/v1beta1 kind: Deployment metadata: annotations: admission-webhook-example.qikqiak.com/status: mutated deployment.kubernetes.io/revision: "1" creationTimestamp: 2018-09-24T11:35:50Z generation: 1 labels: app.kubernetes.io/component: not_available app.kubernetes.io/instance: not_available app.kubernetes.io/managed-by: not_available app.kubernetes.io/name: not_available app.kubernetes.io/part-of: not_available app.kubernetes.io/version: not_available ... ``` 最后,我们重新创建 validating webhook,来一起测试。现在,尝试再次创建 sleep 应用。正常是可以创建成功的,我们可以查看下[admission-controllers 的文档](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#what-are-they) > 准入控制分两个阶段进行,第一阶段,运行 mutating admission 控制器,第二阶段运行 validating admission 控制器。 所以 mutating webhook 在第一阶段添加上缺失的 labels 标签,然后 validating webhook 在第二阶段就不会拒绝这个 deployment 了,因为标签已经存在了,用`not_available`设置他们的值。 ```shell $ kubectl create -f deployment/validatingwebhook-ca-bundle.yaml validatingwebhookconfiguration.admissionregistration.k8s.io "validation-webhook-example-cfg" created $ kubectl create -f deployment/sleep.yaml deployment.apps "sleep" created ``` ## 参考文档 [![优点知识](https://picdn.youdianzhishi.com/images/1731310516334.png)](https://youdianzhishi.com)[![快课星球](https://picdn.youdianzhishi.com/images/1731310722881.png) ](https://fastclass.cn)[![ReadGenius](https://picdn.youdianzhishi.com/images/1731312928462.png) ](https://readgenius.net)[![JoyGames.io](https://picdn.youdianzhishi.com/images/1731311449952.png)](https://www.joygames.io) ## 微信公众号 扫描下面的二维码关注我们的微信公众帐号,在微信公众帐号中回复◉加群◉即可加入到我们的 kubernetes 讨论群里面共同学习。 ![wechat-account-qrcode](https://picdn.youdianzhishi.com/images/qrcode.png)
yg9538
2024年12月12日 11:05
333
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
PDF文档
PDF文档(打印)
分享
链接
类型
密码
更新密码