Admission Webhook ๅผ€ๅ‘

1. Admission Webhooks ๆ˜ฏไป€ไนˆ

่ฏทๆฑ‚้“พ่ทฏ๏ผš

็”จๆˆท่ฏทๆฑ‚  โ†’  Mutating ๅ…ณๅก  โ†’  Validating ๅ…ณๅก  โ†’  etcd

Webhook ๆŒ‡็š„ๆ˜ฏไธญ้€”ๆ‹ฆๆˆช่ฏทๆฑ‚ๅšๅฎšๅˆถๅŒ–ๅค„็†๏ผŒๅŒ…ๅซไธค็ง็ฑปๅž‹๏ผš

Mutate ไฟฎๆ”น

ๆˆช่ƒก่ฏทๆฑ‚ไน‹ๅŽๅšไฟฎๆ”น๏ผŒๆ”นๅฎŒไน‹ๅŽๆ‰”ๅ›žๅŽŸ้“พ่ทฏใ€‚

Validate ๆ ก้ชŒ

ๅฏน่ฏทๆฑ‚็š„ๆ•ฐๆฎๅšๆ ก้ชŒ๏ผŒไธ็ฌฆๅˆ่ง„ๅฎš็š„็›ดๆŽฅๆ‰”ๆމใ€‚

๐Ÿ’ก Admission ็ฟป่ฏ‘ไธบ"ๅ‡†ๅ…ฅ"๏ผŒ"ๅ…ฅ"ๆŒ‡็š„ๆ˜ฏๅ…ฅๅˆฐ etcd ๆ•ฐๆฎๅบ“ไธญใ€‚

2. Webhook ็š„ๆ‰ง่กŒๆ—ถๆœบ

Admission Webhook ๆœฌ่ดจๆ˜ฏ API Server ็š„ไธ€ไธช webhook ่ฐƒ็”จใ€‚ๆ€ป็ป“ๅ‡บไธค็ง webhook ็š„่งฆๅ‘ๆ—ถๆœบ๏ผš

  1. ๆ€ปไฝ“ๆฅ่ฏด๏ผŒAdmission Webhooks ๆœบๅˆถๆ˜ฏๅœจ API Server ๆŽฅๆ”ถๅˆฐ่ฏทๆฑ‚ใ€ๆ‰ง่กŒๆŽˆๆƒๆฃ€ๆŸฅๅŽๅ’Œๅฐ†่ฏทๆฑ‚ๆŒไน…ๅŒ–ๅˆฐ etcd ไน‹ๅ‰่งฆๅ‘ใ€‚
  2. ่ฏฆ็ป†ๅœฐ็œ‹๏ผŒMutating Webhook ๅœจ Validating Webhook ไน‹ๅ‰่งฆๅ‘๏ผŒๅ› ๆญคๅฏไปฅๅ…ˆๅฏน่ต„ๆบ่ฟ›่กŒไฟฎๆ”น๏ผŒ็„ถๅŽๅ†่ฟ›่กŒ้ชŒ่ฏใ€‚

3. Admission Webhook ็š„่ฟ่กŒๆต็จ‹

Pod ๅˆ›ๅปบๆต็จ‹ไธญ Webhook ๆ‰ง่กŒๆ—ถๆœบ๏ผš

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ kubectl apply -f pod.yaml โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ–ผ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ 1. Authentication๏ผˆ่ฎค่ฏ๏ผ‰ โ”‚ โ”‚ "ไฝ ๆ˜ฏ่ฐ๏ผŸ" โ€” ้ชŒ่ฏ็”จๆˆท/ๆœๅŠก่บซไปฝ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ–ผ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ 2. Authorization๏ผˆๆŽˆๆƒ๏ผ‰ โ”‚ โ”‚ "ไฝ ่ƒฝๅš่ฟ™ไปถไบ‹ๅ—๏ผŸ" โ€” RBAC ๆฃ€ๆŸฅ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ–ผ โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•— โ•‘ 3. Admission Control๏ผˆๅ‡†ๅ…ฅๆŽงๅˆถ๏ผ‰ โ•‘ โ•‘ โ•‘ โ•‘ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ•‘ โ•‘ โ”‚ 3a. Mutating Admission Webhook โšก ๅฏไฟฎๆ”นๅฏน่ฑก โ”‚ โ•‘ โ•‘ โ”‚ โ€ข ่‡ชๅŠจๆณจๅ…ฅ sidecar ๅฎนๅ™จ๏ผˆๅฆ‚ Istio๏ผ‰ โ”‚ โ•‘ โ•‘ โ”‚ โ€ข ่‡ชๅŠจๆŒ‚่ฝฝ secret / configmap โ”‚ โ•‘ โ•‘ โ”‚ โ€ข ่ฎพ็ฝฎ้ป˜่ฎค label / annotation โ”‚ โ•‘ โ•‘ โ”‚ โ€ข ไฟฎๆ”น่ต„ๆบ้™ๅˆถ๏ผˆLimitRanger๏ผ‰ โ”‚ โ•‘ โ•‘ โ”‚ โ€ข ๅผบๅˆถ่ฎพ็ฝฎ nodeSelector / toleration โ”‚ โ•‘ โ•‘ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ•‘ โ•‘ โ–ผ โ•‘ โ•‘ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ•‘ โ•‘ โ”‚ 3b. Object Schema Validation โœ… ๅฏน่ฑกๆ ผๅผๆ ก้ชŒ โ”‚ โ•‘ โ•‘ โ”‚ โ€ข ๆฃ€ๆŸฅไฟฎๆ”นๅŽ็š„ๅฏน่ฑกๆ˜ฏๅฆ็ฌฆๅˆ API Schema โ”‚ โ•‘ โ•‘ โ”‚ โ€ข ๅฟ…ๅกซๅญ—ๆฎตใ€็ฑปๅž‹ใ€ๆ ผๅผ้ชŒ่ฏ โ”‚ โ•‘ โ•‘ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ•‘ โ•‘ โ–ผ โ•‘ โ•‘ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ•‘ โ•‘ โ”‚ 3c. Validating Admission Webhook โœ… ๅช่ƒฝๆ ก้ชŒ๏ผŒไธๅฏไฟฎๆ”น โ”‚ โ•‘ โ•‘ โ”‚ โ€ข ๆฃ€ๆŸฅ้•œๅƒๆฅๆบๆ˜ฏๅฆๅˆ่ง„๏ผˆๅชๅ…่ฎธๅ…ฌๅธ้•œๅƒไป“ๅบ“๏ผ‰ โ”‚ โ•‘ โ•‘ โ”‚ โ€ข ๆ ก้ชŒ่ต„ๆบ้…้ขๆ˜ฏๅฆ่ถ…ๆ ‡ โ”‚ โ•‘ โ•‘ โ”‚ โ€ข ๆฃ€ๆŸฅๅฎ‰ๅ…จ็ญ–็•ฅ๏ผˆ็ฆๆญข็‰นๆƒๅฎนๅ™จใ€็ฆๆญข hostPath๏ผ‰ โ”‚ โ•‘ โ•‘ โ”‚ โ€ข ๅ‘ฝๅ่ง„่Œƒๆฃ€ๆŸฅ โ”‚ โ•‘ โ•‘ โ”‚ โ€ข ๆ‹’็ป โ†’ ็›ดๆŽฅ่ฟ”ๅ›ž 403, ๆ•ดไธช่ฏทๆฑ‚ๅคฑ่ดฅ โ”‚ โ•‘ โ•‘ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ•‘ โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• โ”‚ โ–ผ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ 4. Persist to etcd๏ผˆๆŒไน…ๅŒ–๏ผ‰ โ”‚ โ”‚ ๅฏน่ฑกๅ†™ๅ…ฅ etcd๏ผŒๆˆไธบ"ๅทฒ็กฎ่ฎค"็Šถๆ€ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ–ผ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ 5. Scheduler๏ผˆ่ฐƒๅบฆ๏ผ‰ โ”‚ โ”‚ ไธบ Pod ้€‰ๆ‹ฉๅˆ้€‚็š„ Node โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ–ผ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ 6. Kubelet๏ผˆ่ฟ่กŒ๏ผ‰ โ”‚ โ”‚ ๅœจ็›ฎๆ ‡ Node ไธŠๅˆ›ๅปบๅนถๅฏๅŠจๅฎนๅ™จ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

4. Mutating Webhook ่ฟ”ๅ›žๆ ผๅผ

Mutating Webhook ็š„่ฟ”ๅ›žๆ ผๅผๆ˜ฏไธ€ไธช AdmissionReview ๅฏน่ฑก๏ผŒไธŽ่ฏทๆฑ‚ไฝฟ็”จ็›ธๅŒ็š„็ฑปๅž‹๏ผŒๅชๆ˜ฏๅกซๅ……ไบ† response ้ƒจๅˆ†ใ€‚

ๅฎŒๆ•ด็ป“ๆž„

JSON{
  "kind": "AdmissionReview",
  "apiVersion": "admission.k8s.io/v1",
  "response": {
    "uid": "่ฏทๆฑ‚ไธญ็š„ uid๏ผŒๅฟ…้กปๅŽŸๆ ท่ฟ”ๅ›ž",
    "allowed": true,
    "patchType": "JSONPatch",
    "patch": "Base64 ็ผ–็ ็š„ JSON Patch",
    "status": {
      "code": 200,
      "message": "ๅฏ้€‰็š„็Šถๆ€ไฟกๆฏ"
    }
  }
}

ๅ„ๅญ—ๆฎต่ฏดๆ˜Ž

ๅญ—ๆฎตๆ˜ฏๅฆๅฟ…้œ€่ฏดๆ˜Ž
uid ๅฟ…้œ€ ไปŽ่ฏทๆฑ‚ไธญๅŽŸๆ ทๅคๅˆถ๏ผŒAPI Server ้ ๅฎƒๅ…ณ่”่ฏทๆฑ‚ๅ’Œๅ“ๅบ”
allowed ๅฟ…้œ€ true ๆ”พ่กŒ๏ผŒfalse ๆ‹’็ป
patchType ๆกไปถๅฟ…้œ€ allowed: true ไธ”่ฆไฟฎๆ”น่ต„ๆบๆ—ถๅฟ…้กปๆŒ‡ๅฎš๏ผŒ็›ฎๅ‰ๅชๆ”ฏๆŒ JSONPatch
patch ๆกไปถๅฟ…้œ€ Base64 ็ผ–็ ็š„ JSON Patch ๆ•ฐ็ป„๏ผŒไธŽ patchType ้…ๅฏนไฝฟ็”จ
status ๅฏ้€‰ ไป…ๅœจ allowed: false ๆ—ถๆœ‰ๆ„ไน‰๏ผŒ่ฏดๆ˜Žๆ‹’็ปๅŽŸๅ› 

ไธ‰็งๅ…ธๅž‹ๅ“ๅบ”

โ‘  ๆ”พ่กŒๅนถไฟฎๆ”น๏ผˆMutate๏ผ‰

JSON{
  "kind": "AdmissionReview",
  "apiVersion": "admission.k8s.io/v1",
  "response": {
    "uid": "abc-123",
    "allowed": true,
    "patchType": "JSONPatch",
    "patch": "W3sib3AiOiJhZGQiLCJwYXRoIjoiL21ldGFkYXRhL2xhYmVscy9lbnZpcm9ubWVudCIsInZhbHVlIjoicHJvZHVjdGlvbiJ9XQ=="
  }
}

โ‘ก ็›ดๆŽฅๆ‹’็ป

JSON{
  "kind": "AdmissionReview",
  "apiVersion": "admission.k8s.io/v1",
  "response": {
    "uid": "abc-123",
    "allowed": false,
    "status": {
      "code": 403,
      "message": "Pod ไธๅ…่ฎธไฝฟ็”จ latest ๆ ‡็ญพ"
    }
  }
}

โ‘ข ็›ดๆŽฅๆ”พ่กŒ๏ผˆไธไฟฎๆ”น๏ผ‰

JSON{
  "kind": "AdmissionReview",
  "apiVersion": "admission.k8s.io/v1",
  "response": {
    "uid": "abc-123",
    "allowed": true
  }
}

๐Ÿ’ก ๅ…ณไบŽ patch ๅญ—ๆฎต๏ผšpatch ๅญ—ๆฎต็š„ๅ€ผๆ˜ฏ Base64 ็ผ–็ ๅŽ็š„ JSON Patch๏ผŒ่งฃ็ ๅŽๅฐฑๆ˜ฏๆ ‡ๅ‡†็š„ RFC 6902 ๆ ผๅผใ€‚

JSON Patch (RFC 6902)[
  {"op": "add", "path": "/metadata/labels/environment", "value": "production"}
]

5. ไปฃ็ ็คบไพ‹

webhook.py

Pythonfrom flask import Flask, request, jsonify
import json
import ssl
import base64

app = Flask(__name__)


def create_patch(metadata):
    """
    ๅˆ›ๅปบ JSON Patch ไปฅๆทปๅŠ  'mutate' ๆณจ้‡Šใ€‚
    ๅฆ‚ๆžœ metadata.annotations ไธๅญ˜ๅœจ๏ผŒๅˆ™้ฆ–ๅ…ˆๅˆ›ๅปบ่ฏฅ่ทฏๅพ„ใ€‚
    """
    if 'labels' in metadata:
        dic = metadata['labels']
    else:
        dic = {}

    patch = [
        {'op': 'add', 'path': '/metadata/labels', 'value': dic},
        {'op': 'add', 'path': '/metadata/labels/environment', 'value': 'production'}
    ]

    patch_json = json.dumps(patch)
    patch_base64 = base64.b64encode(patch_json.encode('utf-8')).decode('utf-8')
    return patch_base64


@app.route('/mutate', methods=['POST'])
def mutate():
    """
    ๅค„็† Mutating Webhook ็š„่ฏทๆฑ‚๏ผŒๅฏน Pod ๅฏน่ฑกๅบ”็”จ JSON Patchใ€‚
    """
    admission_review = request.get_json()

    if 'request' not in admission_review or 'object' not in admission_review['request']:
        return jsonify({
            'kind': 'AdmissionReview',
            'apiVersion': 'admission.k8s.io/v1',
            'response': {
                'allowed': False,
                'status': {'message': 'Invalid AdmissionReview format'}
            }
        })

    req = admission_review['request']
    metadata = req['object']['metadata']
    patch_json = create_patch(metadata)

    admission_response = {
        'kind': 'AdmissionReview',
        'apiVersion': 'admission.k8s.io/v1',
        'response': {
            'uid': req['uid'],
            'allowed': True,
            'patchType': 'JSONPatch',
            'patch': patch_json
        }
    }

    return jsonify(admission_response)


if __name__ == '__main__':
    context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
    context.load_cert_chain('/certs/tls.crt', '/certs/tls.key')
    app.run(host='0.0.0.0', port=443, ssl_context=context)

6. Validating Webhook ไปฃ็ ็คบไพ‹

ๅ’Œ Mutating Webhook ไธๅŒ๏ผŒValidating Webhook ๅชๅšๆ ก้ชŒใ€ไธไฟฎๆ”นๅฏน่ฑก๏ผŒๅ“ๅบ”ไธญไธ้œ€่ฆ patch / patchType๏ผŒๅช้œ€่ฆ่ฟ”ๅ›ž allowed: true/falseใ€‚

ไธ‹้ขๅฎž็Žฐไธ€ไธชๅธธ่ง็š„ๅฎ‰ๅ…จ็ญ–็•ฅ๏ผš็ฆๆญข Pod ไฝฟ็”จ :latest ้•œๅƒๆ ‡็ญพ + ็ฆๆญข็‰นๆƒๅฎนๅ™จ + ็ฆๆญข hostPath ๆŒ‚่ฝฝใ€‚

validate_webhook.py

Pythonfrom flask import Flask, request, jsonify
import ssl

app = Flask(__name__)

# ========== ๆ ก้ชŒ่ง„ๅˆ™ ==========

ALLOWED_REGISTRIES = []  # ็•™็ฉบ=ไธ้™ๅˆถ้•œๅƒไป“ๅบ“๏ผ›ๅกซๅ…ฅ ["company.registry.io"] ๅˆ™ๅชๅ…่ฎธ่ฏฅไป“ๅบ“

FORBIDDEN_TAGS = [":latest", ":LATEST", ""]   # ็ฆๆญข็š„้•œๅƒๆ ‡็ญพ

FORBIDDEN_HOST_PATHS = ["/", "/etc", "/var", "/proc", "/sys", "/dev"]  # ็ฆๆญข็š„ hostPath ๅ‰็ผ€


def validate_pod(pod):
    """
    ๅฏน Pod ๅฏน่ฑกๆ‰ง่กŒๆ ก้ชŒ๏ผŒ่ฟ”ๅ›ž (allowed, reason)ใ€‚
    allowed=True ่กจ็คบ้€š่ฟ‡๏ผŒallowed=False ่กจ็คบๆ‹’็ป๏ผŒreason ไธบๆ‹’็ปๅŽŸๅ› ใ€‚
    """
    containers = pod.get('spec', {}).get('containers', [])
    init_containers = pod.get('spec', {}).get('initContainers', [])
    all_containers = containers + init_containers

    # ---- 1. ๆฃ€ๆŸฅ้•œๅƒๆ ‡็ญพ ----
    for container in all_containers:
        image = container.get('image', '')

        # ๆฃ€ๆŸฅๆ˜ฏๅฆไฝฟ็”จไบ†็ฆๆญข็š„ๆ ‡็ญพ
        for tag in FORBIDDEN_TAGS:
            if tag and image.endswith(tag):
                return False, f"ๅฎนๅ™จ '{container['name']}' ไฝฟ็”จไบ†็ฆๆญข็š„้•œๅƒๆ ‡็ญพ: {image}๏ผˆไธๅ…่ฎธ :latest ๆˆ–ๆ— ๆ ‡็ญพ๏ผ‰"

        # ๆฃ€ๆŸฅ้•œๅƒไป“ๅบ“็™ฝๅๅ•๏ผˆๅฆ‚ๆžœ้…็ฝฎไบ†๏ผ‰
        if ALLOWED_REGISTRIES:
            allowed = any(image.startswith(registry) for registry in ALLOWED_REGISTRIES)
            if not allowed:
                return False, f"ๅฎนๅ™จ '{container['name']}' ็š„้•œๅƒ '{image}' ไธๅœจๅ…่ฎธ็š„ไป“ๅบ“ๅˆ—่กจไธญ"

    # ---- 2. ๆฃ€ๆŸฅ็‰นๆƒๅฎนๅ™จ ----
    for container in all_containers:
        security_context = container.get('securityContext', {})
        if security_context.get('privileged', False):
            return False, f"ๅฎนๅ™จ '{container['name']}' ไฝฟ็”จไบ†็‰นๆƒๆจกๅผ๏ผˆprivileged: true๏ผ‰๏ผŒ่ฟ™ๆ˜ฏ่ขซ็ฆๆญข็š„"

        if security_context.get('runAsUser', -1) == 0:
            return False, f"ๅฎนๅ™จ '{container['name']}' ไปฅ root ็”จๆˆท่ฟ่กŒ๏ผˆrunAsUser: 0๏ผ‰๏ผŒ่ฟ™ๆ˜ฏ่ขซ็ฆๆญข็š„"

    # ---- 3. ๆฃ€ๆŸฅ hostPath ๆŒ‚่ฝฝ ----
    volumes = pod.get('spec', {}).get('volumes', [])
    for volume in volumes:
        host_path = volume.get('hostPath', {})
        if host_path:
            path = host_path.get('path', '')
            for forbidden in FORBIDDEN_HOST_PATHS:
                if path == forbidden or path.startswith(forbidden + '/'):
                    return False, f"Pod ไฝฟ็”จไบ†ๅฑ้™ฉ็š„ hostPath ๆŒ‚่ฝฝ: {path}๏ผˆ็ฆๆญขๆŒ‚่ฝฝๅฎฟไธปๆœบๆ•ๆ„Ÿ็›ฎๅฝ•๏ผ‰"

    # ---- 4. ๆฃ€ๆŸฅ hostNetwork / hostPID / hostIPC ----
    pod_spec = pod.get('spec', {})
    if pod_spec.get('hostNetwork', False):
        return False, "Pod ไฝฟ็”จไบ† hostNetwork: true๏ผŒ่ฟ™ๆ˜ฏ่ขซ็ฆๆญข็š„"
    if pod_spec.get('hostPID', False):
        return False, "Pod ไฝฟ็”จไบ† hostPID: true๏ผŒ่ฟ™ๆ˜ฏ่ขซ็ฆๆญข็š„"
    if pod_spec.get('hostIPC', False):
        return False, "Pod ไฝฟ็”จไบ† hostIPC: true๏ผŒ่ฟ™ๆ˜ฏ่ขซ็ฆๆญข็š„"

    return True, ""


# ========== HTTP Handler ==========

@app.route('/validate', methods=['POST'])
def validate():
    """
    ๅค„็† Validating Webhook ็š„่ฏทๆฑ‚๏ผŒๅฏน Pod ๅฏน่ฑกๆ‰ง่กŒๅฎ‰ๅ…จ็ญ–็•ฅๆ ก้ชŒใ€‚
    """
    admission_review = request.get_json()

    # ้ชŒ่ฏ่ฏทๆฑ‚ๆ ผๅผ
    if 'request' not in admission_review or 'object' not in admission_review['request']:
        return jsonify({
            'kind': 'AdmissionReview',
            'apiVersion': 'admission.k8s.io/v1',
            'response': {
                'allowed': False,
                'status': {'message': 'Invalid AdmissionReview format'}
            }
        })

    req = admission_review['request']
    pod = req['object']

    # ๆ‰ง่กŒๆ ก้ชŒ
    allowed, reason = validate_pod(pod)

    if allowed:
        # ๆ”พ่กŒ๏ผšไธ้œ€่ฆ patch๏ผŒๅช่ฟ”ๅ›ž allowed: true
        admission_response = {
            'kind': 'AdmissionReview',
            'apiVersion': 'admission.k8s.io/v1',
            'response': {
                'uid': req['uid'],
                'allowed': True
            }
        }
    else:
        # ๆ‹’็ป๏ผš่ฟ”ๅ›ž allowed: false + ๆ‹’็ปๅŽŸๅ› 
        admission_response = {
            'kind': 'AdmissionReview',
            'apiVersion': 'admission.k8s.io/v1',
            'response': {
                'uid': req['uid'],
                'allowed': False,
                'status': {
                    'code': 403,
                    'message': reason
                }
            }
        }

    print(f"[VALIDATE] allowed={allowed}, reason={reason or 'N/A'}")
    return jsonify(admission_response)


if __name__ == '__main__':
    context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
    context.load_cert_chain('/certs/tls.crt', '/certs/tls.key')
    app.run(host='0.0.0.0', port=443, ssl_context=context)

Mutating vs Validating ไปฃ็ ๅฏนๆฏ”

Mutating

  • ้œ€่ฆ patch + patchType ๅญ—ๆฎต
  • ่ฟ”ๅ›ž Base64 ็ผ–็ ็š„ JSON Patch
  • ๅฏไปฅไฟฎๆ”นๅฏน่ฑก็š„ไปปๆ„ๅญ—ๆฎต
  • ๅœจ Validating ไน‹ๅ‰ๆ‰ง่กŒ

Validating

  • ๅช้œ€่ฆ allowed: true/false
  • ไธ้œ€่ฆ patch ๅ’Œ patchType
  • ๅช่ƒฝๅˆคๆ–ญๆ”พ่กŒ/ๆ‹’็ป๏ผŒไธ่ƒฝไฟฎๆ”นๅฏน่ฑก
  • ๅœจ Mutating ไน‹ๅŽๆ‰ง่กŒ

ๆ ก้ชŒ่ง„ๅˆ™่ฏดๆ˜Ž

่ง„ๅˆ™ๆฃ€ๆŸฅๅ†…ๅฎนๆ‹’็ป็คบไพ‹
้•œๅƒๆ ‡็ญพ ็ฆๆญข :latest ๅ’Œๆ— ๆ ‡็ญพ้•œๅƒ nginx:latest โ†’ ๆ‹’็ป
้•œๅƒไป“ๅบ“ ๅฏ้…็ฝฎไป“ๅบ“็™ฝๅๅ•๏ผˆ็•™็ฉบๅˆ™ไธ้™๏ผ‰ docker.io/nginx โ†’ ๆ‹’็ป๏ผˆ่‹ฅ็™ฝๅๅ•ไธๅซ docker.io๏ผ‰
็‰นๆƒๅฎนๅ™จ ็ฆๆญข privileged: true ๅ’Œ runAsUser: 0 ่ฎพ็ฝฎไบ† securityContext.privileged: true โ†’ ๆ‹’็ป
hostPath ็ฆๆญขๆŒ‚่ฝฝๅฎฟไธปๆœบๆ•ๆ„Ÿ็›ฎๅฝ• hostPath: /var/run/docker.sock โ†’ ๆ‹’็ป
host ็ฝ‘็ปœ ็ฆๆญข hostNetwork/hostPID/hostIPC hostNetwork: true โ†’ ๆ‹’็ป

ValidatingWebhookConfiguration

ๅ’Œ MutatingWebhookConfiguration ็ป“ๆž„ๅ‡ ไนŽไธ€ๆ ท๏ผŒๅชๆ˜ฏ kind ไธๅŒใ€path ๆŒ‡ๅ‘ /validate๏ผš

YAMLapiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: example-validating-webhook
webhooks:
  - name: validate.example.webhook.com
    clientConfig:
      service:
        name: webhook-service
        namespace: default
        path: "/validate"                 # ๆŒ‡ๅ‘ /validate ่€Œ้ž /mutate
      caBundle: "..."                     # ๅŒไธ€ไธช CA ่ฏไนฆ
    rules:
      - operations: ["CREATE", "UPDATE"]  # ๅˆ›ๅปบๅ’Œๆ›ดๆ–ฐ้ƒฝๆ ก้ชŒ
        apiGroups: [""]
        apiVersions: ["v1"]
        resources: ["pods"]
    admissionReviewVersions: ["v1"]
    sideEffects: None
    failurePolicy: Fail                   # webhook ไธๅฏ็”จๆ—ถๆ‹’็ป่ฏทๆฑ‚๏ผˆๅฎ‰ๅ…จไผ˜ๅ…ˆ๏ผ‰
    timeoutSeconds: 5

๐Ÿ’ก failurePolicy ็š„้€‰ๆ‹ฉ๏ผš

Fail โ€” webhook ไธๅฏ็”จๆ—ถๆ‹’็ป่ฏทๆฑ‚๏ผˆๅฎ‰ๅ…จไผ˜ๅ…ˆ๏ผŒๆŽจ่็”จไบŽๅฎ‰ๅ…จ็ญ–็•ฅ๏ผ‰

Ignore โ€” webhook ไธๅฏ็”จๆ—ถ่ทณ่ฟ‡ๆ ก้ชŒ๏ผˆๅฏ็”จๆ€งไผ˜ๅ…ˆ๏ผŒๆŽจ่็”จไบŽ้žๅ…ณ้”ฎ็ญ–็•ฅ๏ผ‰

ๅฏนไบŽๅฎ‰ๅ…จ็ฑปๆ ก้ชŒ๏ผˆ็ฆๆญข็‰นๆƒๅฎนๅ™จ็ญ‰๏ผ‰๏ผŒๅบ”่ฏฅ็”จ Fail๏ผŒ้ฟๅ… webhook ๆ•…้šœๆ—ถไธๅฎ‰ๅ…จ็š„ Pod ๆ‚„ๆ‚„้€š่ฟ‡ใ€‚

ไธค็ง Webhook ้ƒจ็ฝฒๅœจไธ€่ตท

ๅฎž้™…้กน็›ฎไธญ๏ผŒMutating ๅ’Œ Validating ้€šๅธธ้ƒจ็ฝฒๅœจๅŒไธ€ไธช Service ้‡Œ๏ผŒๅ…ฑ็”จ่ฏไนฆๅ’Œ Pod๏ผŒๅชๆ˜ฏ่ทฏๅพ„ไธๅŒ๏ผš

Python โ€” ๅˆๅนถ็‰ˆๆœฌfrom flask import Flask, request, jsonify
import json, ssl, base64

app = Flask(__name__)

# ... create_patch() ๅ’Œ validate_pod() ็š„ไปฃ็ ๅŒไธŠ ...

@app.route('/mutate', methods=['POST'])
def mutate():
    # ... Mutating ้€ป่พ‘ ...

@app.route('/validate', methods=['POST'])
def validate():
    # ... Validating ้€ป่พ‘ ...

if __name__ == '__main__':
    context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
    context.load_cert_chain('/certs/tls.crt', '/certs/tls.key')
    app.run(host='0.0.0.0', port=443, ssl_context=context)

ๅฏนๅบ”็š„ K8s ่ต„ๆบๅ…ณ็ณป๏ผš

1 ไธช Pod Flask ๅบ”็”จๅŒๆ—ถ็›‘ๅฌ /mutate ๅ’Œ /validate ไธคไธช่ทฏๅพ„
1 ไธช Service webhook-service:443 โ†’ ่ฝฌๅ‘ๅˆฐ Pod:443
1 ๅฅ—่ฏไนฆ ๅŒไธ€ไธช TLS ่ฏไนฆ๏ผŒๅŒไธ€ไธช Secret
2 ไธช WebhookConfiguration MutatingWebhookConfiguration๏ผˆpath: /mutate๏ผ‰+ ValidatingWebhookConfiguration๏ผˆpath: /validate๏ผ‰

7. ๅˆถไฝœ้•œๅƒ

Dockerfile

DockerfileFROM python:3.9-slim
WORKDIR /app
COPY webhook.py .
RUN pip install Flask -i https://pypi.tuna.tsinghua.edu.cn/simple
CMD ["python", "webhook.py"]

ๆž„ๅปบ & ๆŽจ้€

Shelldocker tag webhook:latest uhub.service.ucloud.cn/mute-webhook/mute:latest
docker push uhub.service.ucloud.cn/mute-webhook/mute:latest

8. ็”Ÿๆˆ่ฏไนฆ

Shell โ€” gen-webhook-cert.sh#!/bin/bash
set -e
SERVICE=webhook-service
NAMESPACE=default
SECRET=webhook-certs
DIR=certs

rm -rf ${DIR}
mkdir -p ${DIR} && cd ${DIR}

echo "===== 1. ็”Ÿๆˆ CA ====="
openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key \
  -subj "/CN=${SERVICE}.${NAMESPACE}.svc" \
  -days 36500 -out ca.crt

echo "===== 2. ็”Ÿๆˆ Webhook ๆœๅŠก่ฏไนฆ ====="
cat > server.cnf << EOF
[req]
default_bits = 2048
prompt = no
default_md = sha256
req_extensions = v3_req
distinguished_name = dn
[dn]
CN = ${SERVICE}.${NAMESPACE}.svc
[v3_req]
basicConstraints = CA:FALSE
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = ${SERVICE}
DNS.2 = ${SERVICE}.${NAMESPACE}
DNS.3 = ${SERVICE}.${NAMESPACE}.svc
DNS.4 = ${SERVICE}.${NAMESPACE}.svc.cluster.local
EOF

openssl genrsa -out webhook.key 2048
openssl req -new -key webhook.key -out webhook.csr -config server.cnf
openssl x509 -req -in webhook.csr \
  -CA ca.crt -CAkey ca.key -CAcreateserial \
  -out webhook.crt -days 36500 \
  -extensions v3_req -extfile server.cnf

echo "===== 3. ๅˆ›ๅปบ K8s Secret ====="
kubectl create secret tls ${SECRET} \
  --cert=webhook.crt \
  --key=webhook.key \
  --namespace=${NAMESPACE} \
  --dry-run=client -o yaml | kubectl apply -f -

echo ""
echo "===== 4. caBundle๏ผˆ็”จไบŽ WebhookConfiguration๏ผ‰====="
cat ca.crt | base64 | tr -d '\n'
echo ""
echo "===== ๅฎŒๆˆ๏ผ ====="
ls -la ca.crt webhook.crt webhook.key

ๆ‰ง่กŒๆ–นๅผ๏ผš

chmod +x gen-webhook-cert.sh

./gen-webhook-cert.sh

่พ“ๅ‡บ้‡Œ็š„ caBundle ๅ€ผ็›ดๆŽฅ็ฒ˜่ดดๅˆฐ MutatingWebhookConfiguration ็š„ caBundle ๅญ—ๆฎตๅณๅฏใ€‚ๅฆ‚ๆžœ้œ€่ฆๆ”นๆœๅŠกๅๆˆ–ๅ‘ฝๅ็ฉบ้—ด๏ผŒๆ”น่„šๆœฌๅผ€ๅคด็š„ SERVICE ๅ’Œ NAMESPACE ๅ˜้‡ๅฐฑ่กŒใ€‚

9. MutatingWebhookConfiguration

ๅฎƒๅฐฑๆ˜ฏๆ•ดไธชๆœบๅˆถ็š„"ๅ…ณๅก่ง„ๅˆ™"ใ€‚ๅ…‰ๆœ‰ webhook ็จ‹ๅบ่ฟ่กŒ็€ๆ˜ฏไธๅคŸ็š„๏ผŒK8s API Server ๆ นๆœฌไธ็Ÿฅ้“ๅฎƒ็š„ๅญ˜ๅœจ๏ผŒไนŸไธ็Ÿฅ้“ไป€ไนˆๆ—ถๅ€™่ฏฅๆŠŠ่ฏทๆฑ‚่ฝฌๅ‘็ป™ๅฎƒใ€‚MutatingWebhookConfiguration ๅฐฑๆ˜ฏๅ‘Š่ฏ‰ API Server ็š„้‚ฃๅผ "้€š็Ÿฅๅ•"ใ€‚

YAMLapiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: example-mutating-webhook
webhooks:
  - name: example.webhook.com          # webhook ็š„ๅ”ฏไธ€ๆ ‡่ฏ†ๅ
    clientConfig:                       # ๅ‘Š่ฏ‰ API Server ๆ€Žไนˆๆ‰พๅˆฐไฝ ็š„ webhook
      service:
        name: webhook-service           # Service ๅ
        namespace: default              # ๅ‘ฝๅ็ฉบ้—ด
        path: "/mutate"                 # ่ฏทๆฑ‚่ทฏๅพ„
      caBundle: "..."                   # CA ่ฏไนฆ็š„ Base64
    rules:                              # ไป€ไนˆๆƒ…ๅ†ตไธ‹่งฆๅ‘่ฟ™ไธช webhook
      - operations: ["CREATE"]          # ๅˆ›ๅปบ่ต„ๆบๆ—ถ่งฆๅ‘
        apiGroups: [""]                 # ๆ ธๅฟƒ API ็ป„
        apiVersions: ["v1"]             # API ็‰ˆๆœฌ
        resources: ["pods"]             # ๅชๅฏน Pod ็”Ÿๆ•ˆ
    admissionReviewVersions: ["v1"]     # ๆ”ฏๆŒ็š„ AdmissionReview ็‰ˆๆœฌ
    sideEffects: None                   # ๅฃฐๆ˜Ž webhook ๆฒกๆœ‰ๅ‰ฏไฝœ็”จ

ๅ…ณ้”ฎ็‚น้€ไธช่งฃ้‡Š

clientConfig

API Server ๆ€Žไนˆๆ‰พไฝ ็š„ webhookใ€‚่ฟ™้‡Œ็”จ Service ๆ–นๅผ๏ผˆ้›†็พคๅ†…้ƒจ่ฎฟ้—ฎ๏ผ‰๏ผŒไนŸๅฏไปฅ็”จ URL ๆ–นๅผ๏ผˆๅค–้ƒจๅœฐๅ€๏ผ‰ใ€‚

caBundle

ไธบไป€ไนˆ้œ€่ฆ๏ผŸๅ› ไธบ API Server ็”จ HTTPS ่ฐƒ็”จ webhook๏ผŒๅฎƒ้œ€่ฆ้ชŒ่ฏ webhook ่ฏไนฆ็š„ๅˆๆณ•ๆ€งใ€‚caBundle ๅฐฑๆ˜ฏ็ญพๅ‘ webhook ่ฏไนฆ็š„ CA ่ฏไนฆ๏ผŒAPI Server ๆ‹ฟๅฎƒๆฅ้ชŒ่ฏ webhook ็š„ TLS ่ฏไนฆๆ˜ฏๅฆๅฏไฟกใ€‚่ฟ™ๅฐฑๆ˜ฏไน‹ๅ‰็”จ gen-webhook-cert.sh ็”Ÿๆˆ ca.crt ๅนถ Base64 ็ผ–็ ็š„ๅŽŸๅ› ใ€‚

rules

ๆ‹ฆๆˆช่ง„ๅˆ™ใ€‚่ฟ™้‡Œ้…็ฝฎ็š„ๆ˜ฏ๏ผšๅชๆœ‰ๅˆ›ๅปบ Pod ๆ—ถๆ‰่งฆๅ‘๏ผŒไธๅฝฑๅ“ๅ…ถไป–่ต„ๆบๆˆ–ๅ…ถไป–ๆ“ไฝœ๏ผˆๆ›ดๆ–ฐใ€ๅˆ ้™ค๏ผ‰ใ€‚

sideEffects: None

ๅ‘Š่ฏ‰ K8s "ๆˆ‘่ฟ™ไธช webhook ไธไผšไบง็”Ÿๅ‰ฏไฝœ็”จ"๏ผŒๆฏ”ๅฆ‚ไธไผšไฟฎๆ”นๅค–้ƒจ็ณป็ปŸใ€‚่ฟ™ๆ˜ฏ K8s v1 ็š„้‡่ฆ่ฆๆฑ‚ใ€‚

ๆ•ดไฝ“ๆต็จ‹

็”จๆˆทๆไบค Pod API Server ๆ”ถๅˆฐ่ฏทๆฑ‚
ๅŒน้…่ง„ๅˆ™ ๆฃ€ๆŸฅ MutatingWebhookConfiguration ็š„ rules๏ผŒๅŒน้…ๅˆฐ "ๅˆ›ๅปบ Pod"
ๅฎšไฝ Webhook ๆ นๆฎ clientConfig ๆ‰พๅˆฐ webhook-service.default.svc:443/mutate
้ชŒ่ฏ่ฏไนฆ ็”จ caBundle ้ชŒ่ฏ webhook ็š„ TLS ่ฏไนฆ
ๅ‘้€่ฏทๆฑ‚ ๅฐ† Pod ๆ•ฐๆฎๅฐ่ฃ…ๆˆ AdmissionReview POST ็ป™ webhook
่ฟ”ๅ›ž่กฅไธ webhook ่ฟ”ๅ›ž JSON Patch๏ผˆๆณจๅ…ฅ environment=production ๆ ‡็ญพ๏ผ‰
ๆŒไน…ๅŒ– API Server ๅบ”็”จ่กฅไธ๏ผŒPod ๅธฆ็€ๆ–ฐๆ ‡็ญพๅญ˜ๅ…ฅ etcd

ๆ‰€ไปฅ่„šๆœฌ้‡Œๅ…ˆ็”จ base64 -w 0 ca.crt ๆŠŠ CA ่ฏไนฆ็ผ–็ ๏ผŒๅ†ๅกซๅ…ฅ caBundle๏ผŒๅฐฑๆ˜ฏๆŠŠ"้ชŒ่ฏๅ‡ญๆฎ"ไบค็ป™ API Server๏ผŒ่ฎฉๅฎƒ่ƒฝๅฎ‰ๅ…จๅœฐ่ฟžๆŽฅไฝ ็š„ webhookใ€‚

็›ฎๅฝ•