๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ”Ž Kubernetes

Dynamic Admission Controller ์‚ฌ์šฉํ•˜๊ธฐ

by Seongpyo Hong 2021. 4. 29.

์ฟ ๋ฒ„๋„คํ‹ฐ์Šค๋Š” ์„ ์–ธํ˜• ํŒจ๋Ÿฌ๋‹ค์ž„์„ ํ†ตํ•ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ฐฐํฌ ๋ฐ ์šด์˜์„ ๋ณด๋‹ค ์‰ฝ๊ฒŒํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์‹œ์Šคํ…œ ๊ฐœ๋ฐœ ์ธก๋ฉด์—์„œ๋Š” ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค๋ผ๋Š” ์ƒˆ๋กœ์šด ๊ฐœ๋… ๋ฐ ์•„ํ‚คํ…์ณ๋ฅผ ์ดํ•ดํ•ด์•ผํ•˜๊ณ  ์ด๋กœ ์ธํ•ด ์„œ๋น„์Šค์˜ ๋ณต์žก๋„ ๋˜ํ•œ ์ฆ๊ฐ€ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค ํ”Œ๋žซํผ์„ ์ œ๊ณตํ•˜๋Š” Provider์˜ ์ž…์žฅ์—์„œ๋Š” ์šด์˜์— ์žˆ์–ด ๋ชจ๋ฒ” ์‚ฌ๋ก€๋ฅผ ๊ตฌ์ถ•ํ•˜๊ธฐ ์œ„ํ•ด ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ตฌ์„ฑ ๋ฐ ๋ฐฐํฌ ๋ฐฉ๋ฒ•์„ ์ถ”์ƒํ™”์‹œ์ผœ ์ œ๊ณตํ•  ํ•„์š”์„ฑ์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์ถ”์ƒํ™”๋ฅผ ๋‹ฌ์„ฑํ•  ๋•Œ, ์–ด๋Š ์ •๋„์˜ ์ถ”์ƒํ™”๋ฅผ ์ œ๊ณตํ• ์ง€์— ๋Œ€ํ•œ ๊ณ ๋ฏผ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๊ณ ์ˆ˜์ค€์˜ ์ถ”์ƒํ™”๋ฅผ ๋‹ฌ์„ฑํ•˜๋ฉด ๊ฐœ๋ฐœ์ž๊ฐ€ ์šด์˜์— ์žˆ์–ด ๋ชจ๋ฒ” ์‚ฌ๋ก€๋ฅผ ์ค€์ˆ˜ํ•˜๋„๋ก ๋งŒ๋“ค ์ˆ˜ ์žˆ์ง€๋งŒ, ์ง€๋‚˜์นœ ์ถ”์ƒํ™”๋Š” ์„ธ๋ถ€ ์„ค์ •์— ๋Œ€ํ•ด ์ ‘๊ทผ์„ ์–ด๋ ต๊ฒŒํ•˜๊ณ , ๋‹ซํžŒ ์‹œ์Šคํ…œ์ด ๋  ์œ„ํ—˜์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์–ด๋Š์ •๋„์˜ ์ถ”์ƒํ™”๋ฅผ ๋‹ฌ์„ฑํ• ์ง€์— ๋Œ€ํ•œ ๊ณ ๋ฏผ๊ณผ ์„ค๊ณ„๊ฐ€ ๋™๋ฐ˜๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ถ”์ƒํ™”๋ฅผ ์ œ๊ณตํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์ผ๋ฐ˜์ ์œผ๋กœ 2๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ๋Š” ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค๋ฅผ ์™ธ๋ถ€๋กœ ๋ถ€ํ„ฐ ๊ฐ์ถ”๊ณ  ํ”Œ๋žซํผ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฐœ๋ฐœ์ž๋Š” ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค์˜ ์กด์žฌ๋ฅผ ์•Œ์ง€ ๋ชปํ•œ์ฑ„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ๋‘ ๋ฒˆ์งธ๋Š” ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค API ์„œ๋ฒ„๋ฅผ ํ†ตํ•ด ์ƒˆ๋กœ์šด ๋ฆฌ์†Œ์Šค๋ฅผ ๋™์ ์œผ๋กœ ์ถ”๊ฐ€ํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ํ”Œ๋žซํผ์˜ ์‚ฌ์šฉ์ž์˜ ์œ ์Šค ์ผ€์ด์Šค๊ฐ€ ๋ช…ํ™•ํ•˜๊ณ  ์‚ฌ์šฉ ํŽธ์˜์„ฑ์ด ์ค‘์š”ํ•˜๋‹ค๋ฉด ์ฒซ ๋ฒˆ์งธ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ฐฐํฌ์™€ ๊ฐ™์ด ์œ ์Šค์ผ€์ด์Šค๊ฐ€ ๋‹ค์–‘ํ•œ ๊ฒฝ์šฐ์—๋Š” API ์„œ๋ฒ„ ํ™•์žฅ์„ ํ†ตํ•ด ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ด ๋‹ค์–‘ํ•œ ์œ ์Šค์ผ€์ด์Šค์— ๋Œ€์‘ํ•˜๊ธฐ ์ข‹์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ํ™•์žฅ์˜ ๊ฒฝ์šฐ์—๋Š” ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค ์ƒํƒœ๊ณ„์— ์กด์žฌํ•˜๋Š” ๋‹ค์–‘ํ•œ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์žฅ์ ๋„ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.


์ฟ ๋ฒ„๋„คํ‹ฐ์Šค๋ฅผ ํ™•์žฅํ•˜๊ธฐ ์œ„ํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ ์•„๋ž˜์™€ ๊ฐ™์€ ๋ฐฉ๋ฒ•์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

์‚ฌ์ด๋“œ์นด ์ปจํ…Œ์ด๋„ˆ

์‚ฌ์ด๋“œ์นด ์ปจํ…Œ์ด๋„ˆ๋Š” ๋ฉ”์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…Œ์ด๋„ˆ์™€ ํ•จ๊ป˜ ์‹คํ–‰๋˜๋Š” ์ปจํ…Œ์ด๋„ˆ๋กœ์จ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ์•„๋‹Œ ๋ณ„๋„์˜ ๋กœ์ง์„ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•œ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด, ์‚ฌ์ด๋“œ์นด ์ปจํ…Œ์ด๋„ˆ๋ฅผ ํ†ตํ•ด ๋กœ๊ทธ๋ฅผ ์ „์†กํ•˜๊ฑฐ๋‚˜ ์„œ๋น„์Šค ๋ฉ”์‹œ ํ”Œ๋žซํผ์—์„œ ํ”„๋ก์‹œ ๋„คํŠธ์›Œํฌ๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์–ด๋“œ๋ฏธ์…˜ ์ปจํŠธ๋กค๋Ÿฌ

์–ด๋“œ๋ฏธ์…˜ ์ปจํŠธ๋กค๋Ÿฌ๋Š” ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค API ์š”์ฒญ์ด etcd์— ์ €์žฅ๋˜๊ธฐ ์ „์— ์œ ํšจ์„ฑ ๊ฒ€์ฆ๊ณผ ๊ฐ™์€ ์ถ”๊ฐ€ ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. 

CRD

CRD๋Š” ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค ํด๋Ÿฌ์Šคํ„ฐ์— ์‚ฌ์šฉ์ž๊ฐ€ ์ •์˜ํ•œ ์ƒˆ๋กœ์šด ๋ฆฌ์†Œ์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. CRD๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ณ ์ˆ˜์ค€์˜ ์ถ”์ƒํ™”๋œ ๋ฆฌ์†Œ์Šค๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด Elasticsearch๋ผ๋Š” CRD๋ฅผ ์ •์˜ํ•˜๋ฉด ์ด๋ฅผ ์ƒ์„ฑํ•˜๋ฉด ์ž๋™์œผ๋กœ Elasticsearch Cluster๋ฅผ ์ƒ์„ฑํ•ด์ฃผ๋Š” ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. CRD์™€ ๊ด€๋ จ๋œ ๋‚ด์šฉ์€ Operator SDK ๊ด€๋ จ ๊ธ€์„ ํ†ตํ•ด ์ž์„ธํ•œ ๋‚ด์šฉ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


์ด๋ฒˆ ๊ธ€์—์„œ๋Š” ์‚ฌ์ด๋“œ์นด ์ปจํ…Œ์ด๋„ˆ๋‚˜ CRD๋Š” ๊ธฐ์กด์— ์‚ฌ์šฉํ•ด๋ณธ์ ์ด ์žˆ์ง€๋งŒ ์–ด๋“œ๋ฏธ์…˜ ์ปจํŠธ๋กค๋Ÿฌ์— ๋Œ€ํ•ด์„œ๋Š” ์ฒ˜์Œ ์ ‘ํ•ด๋ณด๋Š” ๋‚ด์šฉ์ด๊ธฐ ๋•Œ๋ฌธ์— ์–ด๋“œ๋ฏธ์…˜ ์ปจํŠธ๋กค๋Ÿฌ์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ์•„๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. 

์–ด๋“œ๋ฏธ์…˜ ์ปจํŠธ๋กค๋Ÿฌ?

์–ด๋“œ๋ฏธ์…˜ ์ปจํŠธ๋กค๋Ÿฌ๋Š” API ์š”์ฒญ์„ ๊ฐ€๋กœ์ฑ„ ํŠน์ • ๊ธฐ๋Šฅ์„ ํ™œ์„ฑํ™”ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ๋ฆฌ์†Œ์Šค๋ฅผ ์ •์˜ํ•˜์ง€ ์•Š๊ฑฐ๋‚˜ ๊ธฐ๋ณธ์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” StorageClass๊ฐ€ ๊ฒฐ์ •๋˜๋Š” ๊ฒƒ์˜ ๋‚ด๋ถ€์—๋Š” ๋ชจ๋‘ ์–ด๋“œ๋ฏธ์…˜ ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. 

์•„๋ž˜ ๊ทธ๋ฆผ์€ ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค API๊ฐ€ etcd์— ์ €์žฅ๋˜๊ธฐ ์ „๊นŒ์ง€ ํ๋ฆ„์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. 

์ด ๊ทธ๋ฆผ์—์„œ ์•Œ ์ˆ˜ ์žˆ๋“ฏ์ด ์–ด๋“œ๋ฏธ์…˜ ์ปจํŠธ๋กค๋Ÿฌ๋Š” API ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ณผ์ • ์ค‘ ํ•œ ๋‹จ๊ณ„์ด๋ฉฐ ์š”์ฒญ์„ ๊ฒ€์ฆํ•˜๊ฑฐ๋‚˜ ๋ณ€๊ฒฝํ•˜๋Š” ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (๊ฒ€์ฆ ์–ด๋“œ๋ฏธ์…˜ ์ปจํŠธ๋กค๋Ÿฌ๋Š” ์š”์ฒญ์„ ์ˆ˜์ •ํ•  ์ˆ˜ ์—†๋Š” ๋ฐ˜๋ฉด์— ๋ณ€๊ฒฝ ์–ด๋“œ๋ฏธ์…˜ ์ปจํŠธ๋กค๋Ÿฌ๋Š” ์š”์ฒญ์„ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.)

์ข…๋ฅ˜

์–ด๋“œ๋ฏธ์…˜ ์ปจํŠธ๋กค๋Ÿฌ์—๋Š” standard์™€ dynamic์ด๋ผ๋Š” 2๊ฐ€์ง€ ๋“ฑ๊ธ‰์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. standard๋Š” API ์„œ๋ฒ„์™€ ํ•จ๊ป˜ ์ปดํŒŒ์ผ๋˜๊ณ , API๊ฐ€ ์‹œ์ž‘๋  ๋•Œ ์„ค์ •ํ•ด์•ผํ•˜๋Š” ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. dynamic๋Š” ๋Ÿฐํƒ€์ž„ ์‹œ์ ์— ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋Š” ์ปจํŠธ๋กค๋Ÿฌ๋กœ์จ ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค์˜ ์™ธ๋ถ€์—์„œ ๊ฐœ๋ฐœ๋ฉ๋‹ˆ๋‹ค. (dynamic ์–ด๋“œ๋ฏธ์…˜ ์ปจํŠธ๋กค๋Ÿฌ๋Š” HTTP ์ฝœ๋ฐฑ์„ ํ†ตํ•ด ์š”์ฒญ์„ ๋ฐ›๋Š” webhook์ด ์œ ์ผํ•˜๊ฒŒ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.)

Standard

์ฟ ๋ฒ„๋„คํ‹ฐ์Šค์—๋Š” default๋กœ ์ œ๊ณต๋˜๋Š” standard ์–ด๋“œ๋ฏธ์…˜ ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. ๊ฐ๊ฐ์˜ ์–ด๋“œ๋ฏธ์…˜ ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ ์ˆ˜ํ–‰ํ•˜๋Š” ์—ญํ• ์€ ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค ๋ฌธ์„œ์—์„œ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
API ์„œ๋ฒ„์— `--enable-admission-plugins=<controller-name-1>,<controller-name-2>`์˜ ํ˜•ํƒœ๋กœ ์„ค์ •์„ ์ถ”๊ฐ€ํ•จ์œผ๋กœ์จ ์›ํ•˜๋Š” ์–ด๋“œ๋ฏธ์…˜ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ํ™œ์„ฑํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 

Dynamic

์ฟ ๋ฒ„๋„คํ‹ฐ์Šค์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋ณธ ์–ด๋“œ๋ฏธ์…˜ ์ปจํŠธ๋กค๋Ÿฌ์ด์™ธ์— ํŠน์ •ํ•œ ์œ ์Šค์ผ€์ด์Šค๋ฅผ ๋Œ€์‘ํ•˜๊ธฐ ์œ„ํ•œ ์–ด๋“œ๋ฏธ์…˜ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์ •์˜ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋•Œ ์‚ฌ์šฉ๋˜๋Š” ํ”Œ๋Ÿฌ๊ทธ์ธ์ด MutatingAdmissionWebhook๊ณผ ValidatingAdmissionWebhook์ž…๋‹ˆ๋‹ค. MutatiingAdmissionWebhook์€ ์š”์ฒญ์˜ ๋ณ€๊ฒฝ, ValidatingAdmissionWebhook์€ ์š”์ฒญ์˜ ๊ฒ€์ฆ์„ ์ˆ˜ํ–‰ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์œ„์˜ ๊ทธ๋ฆผ์—์„œ์ฒ˜๋Ÿผ webhook ๋ถ€๋ถ„์„ ์ง์ ‘ ๊ฐœ๋ฐœํ•จ์œผ๋กœ์จ ์ปค์Šคํ…€ํ•œ ๋ณ€๊ฒฝ, ๊ฒ€์ฆ ๋กœ์ง์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


Webhook ๋™์ž‘ ๊ณผ์ •

๋จผ์ €, WebHook์ด ๋™์ž‘ํ•˜๋Š” ๊ณผ์ •์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. 

1. Mutating/ValidatingWebhookController๋Š” AdmissionRequest๋ฅผ webhook server์— POST๋กœ ์š”์ฒญ์„ ์ „์†กํ•ฉ๋‹ˆ๋‹ค.

  • AdmissionRequest์—๋Š” AdmissionReview๋ผ๋Š” ๊ฐ์ฒด๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉฐ ์ด ๊ฐ์ฒด๋Š” API server๊ฐ€ ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ๋ฉ”๋‹ˆํŽ˜์ŠคํŠธ๋ฅผ ๋‹ด๊ณ  ์žˆ๋Š” ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค.

2. Webhook Server๋Š” ์›ํ•˜๋Š” ๋กœ์ง์„ ์ˆ˜ํ–‰ํ•˜์—ฌ ์š”์ฒญ์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์ฆํ•˜๊ฑฐ๋‚˜ ์š”์ฒญ์„ ๋ณ€๊ฒฝํ•˜๊ณ  ์ด์— ๋Œ€ํ•œ ์‘๋‹ต์„ AdmissionResponse ํ˜•ํƒœ๋กœ ๋ณด๋ƒ…๋‹ˆ๋‹ค.

  • AdmissionResponse๋Š” webhook server์—์„œ admission controller์—๊ฒŒ ์š”์ฒญ์— ๋Œ€ํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋‹ด๊ณ  ์žˆ๋Š” ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค. AdmissionResponse๋Š” UID, Allowed, Status์™€ ๊ฐ™์€ ํ•„๋“œ๊ฐ€ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. UID๋Š” webhook server๊ฐ€ ๋ฐ›์€ AdmissionReview์™€ ๋™์ผํ•œ UID๋ฅผ ๋ฐ˜ํ™˜ํ•ด์ค˜์•ผํ•ฉ๋‹ˆ๋‹ค. Allowed๋Š” ํ•ด๋‹น ์š”์ฒญ์„ ์Šน์ธํ• ์ง€, ๊ฑฐ์ ˆํ• ์ง€์— ๋Œ€ํ•œ ํ‘œ์‹œ๋กœ ์ด์— ๋Œ€ํ•œ ์ƒํƒœ๊ฐ’์œผ๋กœ Status๋ฅผ ๋„˜๊ฒจ์ฃผ๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. 
  • Allowed๊ฐ€ True์ด๊ณ  MutatingAdmissionController์˜ ๊ฒฝ์šฐ, webhook server๋Š” ์›๋ณธ ์š”์ฒญ์„ ์ˆ˜์ •ํ•˜์—ฌ ์ „์†กํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋•Œ ์ˆ˜์ •๋œ ๋ถ€๋ถ„์„ ์•Œ๋ ค์ฃผ๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ํ˜„์žฌ Kubernetes๋Š” JSON patch ๋ฐฉ๋ฒ•๋งŒ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

JSON Patch

๊ฐ„๋žตํ•˜๊ฒŒ JSON Patch ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์˜ˆ์‹œ๋ฅผ ๋“ค์–ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ, Deployment์˜ replicas์ˆ˜๊ฐ€ ์ง€์ •๋˜์–ด ์žˆ์ง€ ์•Š์€ ๊ฒฝ์šฐ, ๊ธฐ๋ณธ์ ์œผ๋กœ 3์ด๋ผ๋Š” ๊ฐ’์„ ์„ค์ •ํ•ด์ฃผ๋Š” ์ƒํ™ฉ์„ ๊ฐ€์ •ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด ๋•Œ, ํ•ด๋‹นํ•˜๋Š” JSON patch๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

[{"op": "add", "path": "/spec/replicas", "value": 3}]

์ด JSON Patch๋ฅผ ์ „์†กํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” patchType์œผ๋กœ JSONPatch๋ฅผ patch ๊ฐ’์œผ๋กœ ์œ„์˜ JSON์„ base64๋กœ ์ธ์ฝ”๋”ฉํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์–ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ตœ์ข…์ ์œผ๋กœ ์ƒ์„ฑ๋˜๋Š” Response๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

{
  "apiVersion": "admission.k8s.io/v1",
  "kind": "AdmissionReview",
  "response": {
    "uid": "<value from request.uid>",
    "allowed": true,
    "patchType": "JSONPatch",
    "patch": "W3sib3AiOiAiYWRkIiwgInBhdGgiOiAiL3NwZWMvcmVwbGljYXMiLCAidmFsdWUiOiAzfV0="
  }
}

Custom Mutating Webhook ๊ฐœ๋ฐœ

์œ„์˜ ์„ค๋ช…์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์ปค์Šคํ…€ํ•œ mutating์„ ์ˆ˜ํ–‰ํ•˜๋Š” webhook ์„œ๋ฒ„๋ฅผ ๋งŒ๋“ค์–ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์‹œ๋‚˜๋ฆฌ์˜ค๋กœ๋Š” inject annotation์ด ์กด์žฌํ•˜๋Š” deployment๊ฐ€ ์ƒ์„ฑ๋  ๋•Œ, init container๋ฅผ injectํ•ด์ฃผ๋Š” ๊ธฐ๋Šฅ์ด ํ•„์š”ํ•˜๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์ธ์ฆ์„œ ๋ฐœ๊ธ‰

Admission Controller๋Š” Webhook Server์™€ HTTPS๋ฅผ ํ†ตํ•ด ํ†ต์‹ ํ•˜๊ธฐ ๋–„๋ฌธ์—, ๊ฐ€์žฅ ๋จผ์ € ํ•ด์•ผํ•  ์ž‘์—…์€ Webhook Server์—์„œ ํ•„์š”ํ•œ ์ธ์ฆ์„œ๋ฅผ ๋ฐœ๊ธ‰๋ฐ›๋Š” ์ผ์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ์ฃผ์˜ํ•  ์ ์€ Common Name์— ๋Œ€ํ•œ ์„ค์ •์ž…๋‹ˆ๋‹ค. alice_k106๋‹˜์˜ ๊ธ€์„ ์ฐธ๊ณ ํ•˜๋ฉด ์ธ์ฆ์„œ์˜ Common Name์œผ๋กœ ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค ๋‚ด๋ถ€ DNS์—์„œ ์‚ฌ์šฉ๋˜๋Š” ์ด๋ฆ„์„ ์ง€์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ œ๊ฐ€ ์ƒ์„ฑํ•  ์„œ๋น„์Šค์˜ ์ด๋ฆ„์€ mutate-server-svc, ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋Š” mutate-webhook์ด๊ธฐ ๋•Œ๋ฌธ์— ์ตœ์ข…์ ์œผ๋กœ mutate-server-svc.mutate-webhook.svc๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ CN์„ ์„ค์ •ํ•ด์คฌ์Šต๋‹ˆ๋‹ค.

โžœ  mkdir certsopenssl

โžœ req -nodes -new -x509 -keyout certs/ca.key -out certs/ca.crt -subj "/CN=Admission Controller Demo"

โžœ openssl genrsa -out certs/admission-tls.key 2048

โžœ openssl req -new -key certs/admission-tls.key -subj "/CN=mutate-server-svc.mutate-webhook.svc" | openssl x509 -req -CA certs/ca.crt -CAkey certs/ca.key -CAcreateserial -out certs/admission-tls.crt

TLS Secret ์ƒ์„ฑ

๋‹ค์Œ์œผ๋กœ ์œ„์—์„œ ๋ฐœ๊ธ‰๋ฐ›์€ ์ธ์ฆ์„œ๋ฅผ Mutate Webhook Server์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด TLS Secret์„ ์ƒ์„ฑํ•ด์ค๋‹ˆ๋‹ค.

โžœ  kubectl create -n mutate-webhook secret tls admission-tls \ --cert "certs/admission-tls.crt" \ --key "certs/admission-tls.key"

Mutate Webhook Server

์ œ๊ฐ€ ์ˆ˜ํ–‰ํ•  Mutate ๋กœ์ง์€ Deployment์˜ initContainer ํ•„๋“œ์— ์‚ฌ์ „์— ์ •์˜ํ•œ ๋ฉ”๋‹ˆํŽ˜์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๋กœ์ง์ž…๋‹ˆ๋‹ค. github.com/morvencao/kube-mutating-webhook-tutorial ์„ ์ฐธ๊ณ ํ•ด ์ œ๊ฐ€ ํ•„์š”ํ•œ ๋กœ์ง์œผ๋กœ ์ˆ˜์ •ํ•˜์—ฌ ๊ตฌ์ถ•ํ•œ Webhook Server๋Š” Github์—์„œ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 

๊ฐ„๋‹จํ•˜๊ฒŒ ์„ค๋ช…ํ•˜๋ฉด ๋จผ์ € AdmissionReview ๊ฐ์ฒด๋ฅผ ๋ฐ›์•„ Deployment์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์–ป์–ด์˜ต๋‹ˆ๋‹ค.

func (ws WebhookServer) mutate(admissionReview *admissionv1.AdmissionReview) *admissionv1.AdmissionResponse {
	request := admissionReview.Request
	var deployment appv1.Deployment
	if err := json.Unmarshal(request.Object.Raw, &deployment); err != nil {
		log.Errorf("Couldn't unmarshall raw object : %v", err)
		return &admissionv1.AdmissionResponse{
			Result: &metav1.Status{
				Message: err.Error(),
			},
		}
	}
    ....

 

ํ•ด๋‹น Deployment์— ๋Œ€ํ•ด Mutation ๋Œ€์ƒ(Init Container๋ฅผ ์ถ”๊ฐ€ํ•  ๋Œ€์ƒ)์ธ์ง€ ํŒ๋‹จํ•ฉ๋‹ˆ๋‹ค. ์ €๋Š” init-container-injector-webhook.sphong.com/inject = "true" ์–ด๋…ธํ…Œ์ด์…˜์„ ํ†ตํ•ด Mutation ๋Œ€์ƒ์„ ์„ ์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

func isMutationTarget(ignoreNamespaces []string, metadata *metav1.ObjectMeta) bool {
	...

	annotation := metadata.GetAnnotations()
	if annotation == nil {
		annotation = make(map[string]string)
	}

	status := annotation[admissionWebhookAnnotationInjectKey]
	if strings.ToLower(status) == "true" {
		return true
	}
	return false
    
}

 

 

Mutataion Target์ธ ๊ฒฝ์šฐ, init container ํ•„๋“œ์— ๊ธฐ์กด์— ์ •์˜ํ•œ ๋ฉ”๋‹ˆํŽ˜์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•ด์ค๋‹ˆ๋‹ค. ๋˜ํ•œ, ๋‹ค์Œ๋ฒˆ๋ถ€ํ„ฐ mutation์˜ ๋Œ€์ƒ์ด ๋˜์ง€ ์•Š๋„๋ก init-container-injector-webhook.sphong.com/inject = "injected"๋กœ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. ์œ„์—์„œ ์–ธ๊ธ‰ํ–ˆ๋˜ ๊ฒƒ์ฒ˜๋Ÿผ ์ด๋Ÿฐ ์ถ”๊ฐ€, ์—…๋ฐ์ดํŠธ์— ๋Œ€ํ•œ ์ •๋ณด๋Š” JSON Patch๋ฅผ ํ†ตํ•ด ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.

func createPatch(deployment *appv1.Deployment, config *Config, annotations map[string]string) ([]byte, error) {
	var patch []patchOperation
	patch = append(patch, addInitContainer(deployment.Spec.Template.Spec.InitContainers, config.Containers, "/spec/template/spec/initContainers")...)
	patch = append(patch, updateAnnotation(deployment.Annotations, annotations)...)

	return json.Marshal(patch)
}

 

์ตœ์ข…์ ์œผ๋กœ AdmissionReview๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ด์„œ ์‘๋‹ต์„ Admission Controller์—๊ฒŒ ์ „์†กํ•ฉ๋‹ˆ๋‹ค. 

func (ws WebhookServer) Serve(responseWriter http.ResponseWriter, request *http.Request) {
	...
    // ์‘๋‹ต์„ ๋‹ด์„ ๊ฐ์ฒด
    var admissionResponse *admissionv1.AdmissionResponse
    
    // Webhook Server๊ฐ€ ๋ฐ›์€ AdmissionReview ๊ฐ์ฒด (์›๋ณธ)
	originAdmissionReview := admissionv1.AdmissionReview{}
    
    // Deserializer 
	if _, _, err := deserializer.Decode(requestBody, nil, &originAdmissionReview); err != nil {
		log.Errorf("Can't decode request body: %v", err)
		admissionResponse = &admissionv1.AdmissionResponse{
			Result: &metav1.Status{
				Message: err.Error(),
			},
		}
	} else {
    	// Mutate Login ์ˆ˜ํ–‰
		admissionResponse = ws.mutate(&originAdmissionReview)
	}

	// Admission Review ๊ฐ์ฒด์˜ TypeMeta ํ•„๋“œ๊ฐ€ Required์ด๊ธฐ ๋•Œ๋ฌธ์— ๋ช…์‹œ์ ์œผ๋กœ ์ •์˜ํ•ด์ค˜์•ผ ํ•œ๋‹ค.
	typeMeta := metav1.TypeMeta{
		Kind:       "AdmissionReview",
		APIVersion: "admission.k8s.io/v1",
	}
	
    // ์‘๋‹ต์„ ์œ„ํ•œ AdmissionReview ๊ฐ์ฒด
	mutatedAdmissionReview := admissionv1.AdmissionReview{TypeMeta: typeMeta}
	mutatedAdmissionReview.Response = admissionResponse
	
    // UID๋Š” Request AdmissionReview ๊ฐ์ฒด์˜ UID์™€ ๋™์ผ
    if originAdmissionReview.Request != nil {
		mutatedAdmissionReview.Response.UID = originAdmissionReview.Request.UID
	}
    
    // return json response
    data, err := json.Marshal(mutatedAdmissionReview)
    if _, err := responseWriter.Write(data); err != nil {
		log.Errorf("Can't Write Response : %v", err)
		http.Error(responseWriter, fmt.Sprintf("Can't write response : %v", err), http.StatusInternalServerError)
	}
    ...
    

Webhook Server Deployment

ํ•ด๋‹น ์„œ๋ฒ„์— ๋Œ€ํ•ด Deployment์™€ Service๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.  ์ด ๋•Œ, ์•ž์„œ ์ƒ์„ฑํ•œ tls ์‹œํฌ๋ฆฟ๊ณผ init container์— ๋Œ€ํ•œ ์„ค์ •์„ ๋งˆ์šดํŠธ ํ•ด์ค๋‹ˆ๋‹ค.

Deployment

Service


 

Mutate Webhook Configuration 

๋‹ค์Œ์œผ๋กœ Mutate Webhook์— ๋Œ€ํ•œ ConfigMap์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ด ์„ค์ •์„ ํ†ตํ•ด mutate server์— ๋Œ€ํ•œ ์—ฐ๊ฒฐ ์ •๋ณด์™€ caBundle, ์–ธ์ œ mutate webhook์ด ์‹คํ–‰๋ ์ง€์— ๋Œ€ํ•ด ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 

์œ„์—์„œ ์ƒ์„ฑํ•œ ca.crt๋ฅผ ํ†ตํ•ด CA_BUNDLE ๊ฐ’์„ ์ฑ„์›Œ์ฃผ๊ณ  ConfigMap์„ ์ƒ์„ฑํ•˜๋ฉด ์•ž์œผ๋กœ Deployment๋ฅผ ์ƒ์„ฑํ•  ๋–„๋งˆ๋‹ค Mutate ๋กœ์ง์„ ์ˆ˜ํ–‰ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

โžœ export CA_BUNDLE=$(kubectl get secrets -o jsonpath="{.items[?(@.metadata.annotations['kubernetes\.io/service-account\.name']=='default')].data.ca\.crt}")

โžœ export CA_BUNDLE=$(cat certs/ca.crt | base64 | tr -d '\n')

โžœ cat deploy/webhook-config.yaml | sed "s|\${CA_BUNDLE}|${CA_BUNDLE}|g" | kubectl apply -n mutate-webhook -f -

์ตœ์ข…์ ์œผ๋กœ ์•„๋ž˜์˜ Deployment๋ฅผ ์ƒ์„ฑํ•ด MutatingWebhook์ด ์ •์ƒ์ ์œผ๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ํ™•์ธํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

ํ™•์ธํ•ด๋ณด๋ฉด nginx-deployment์— ์†ํ•˜๋Š” Pod๋“ค์€ Init Container ์„ค์ •์ด ์ถ”๊ฐ€๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 

Result


์ด๋ฒˆ๊ธ€์—์„œ๋Š” ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค์—์„œ ๊ณ ์ˆ˜์ค€์˜ ์ถ”์ƒํ™”๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ• ์ค‘ ํ•˜๋‚˜์ธ Admission Controller์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด์•˜์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ๋œ ๋ชจ๋“  ์ฝ”๋“œ๋Š” Github์—์„œ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ฐธ๊ณ ์ž๋ฃŒ

- kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/

- kubernetes.io/docs/reference/access-authn-authz/admission-controllers/

- blog.naver.com/alice_k106/221546328906

- github.com/morvencao/kube-mutating-webhook-tutorial

๋Œ“๊ธ€