๐น ๊ฐ์
์ด๋ฒ ๊ธ์์๋ Istio๋ฅผ ํ์ฉํ A/B ํ
์คํธ ์ ๋ต์ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
A/B ํ
์คํธ๋ ์ฌ์ฉ์๋ฅผ ๊ทธ๋ฃน์ผ๋ก ๋๋์ด ์๋ก ๋ค๋ฅธ ๋ฒ์ ์ ์ ํ๋ฆฌ์ผ์ด์
์ ์ ๊ณตํ๋ ๊ธฐ๋ฒ์
๋๋ค.
์ด๋ฅผ ํตํด ์๋ก์ด ๊ธฐ๋ฅ์ ํจ๊ณผ๋ฅผ ๊ฒ์ฆํ๊ณ , ๋ฐ์ดํฐ ๊ธฐ๋ฐ์ผ๋ก ์์ฌ ๊ฒฐ์ ์ ๋ด๋ฆด ์ ์์ต๋๋ค.
์ด ๊ธ์์๋ A/B ํ
์คํธ ๊ฐ๋
, Istio์์ A/B ํ
์คํธ๋ฅผ ๊ตฌํํ๋ ๋ฐฉ๋ฒ,
๊ทธ๋ฆฌ๊ณ VirtualService์ DestinationRule์ ํ์ฉํ์ฌ ํธ๋ํฝ์ ํน์ ์ฌ์ฉ์ ๊ทธ๋ฃน์ ๋ง๊ฒ ๋ถ๋ฐฐํ๋ ๋ฐฉ๋ฒ์ ์ค๋ช
ํ๊ฒ ์ต๋๋ค.
๐น 1. A/B ํ ์คํธ๋?
โ 1.1 A/B ํ ์คํธ ๊ฐ๋
A/B ํ
์คํธ๋ ๋ ๊ฐ์ง ์ด์์ ๋ฒ์ ์ ์ด์ํ๋ฉด์, ์ฌ์ฉ์ ๋ฐ์์ ๋น๊ต ๋ถ์ํ๋ ๊ธฐ๋ฒ์
๋๋ค.
์ด ๋ฐฉ์์ ์ฃผ๋ก ์น ์ ํ๋ฆฌ์ผ์ด์
, ๋ชจ๋ฐ์ผ ์ฑ, API ์๋ํฌ์ธํธ ๋ณ๊ฒฝ์ ํ๊ฐํ ๋ ์ฌ์ฉ๋ฉ๋๋ค.
A/B ํ ์คํธ์ ์ฃผ์ ํน์ง
- ์ฌ์ฉ์๋ฅผ ํน์ ๊ทธ๋ฃน(A, B)์ผ๋ก ๋๋์ด ์๋ก ๋ค๋ฅธ ๊ธฐ๋ฅ์ ์ ๊ณต
- A ๊ทธ๋ฃน(๊ธฐ์กด ๋ฒ์ ), B ๊ทธ๋ฃน(์๋ก์ด ๋ฒ์ ) ๊ฐ ์ฑ๋ฅ ๋น๊ต ๊ฐ๋ฅ
- ๋ฐ์ดํฐ ๊ธฐ๋ฐ์ ๊ฐ์ ๋ฐฉํฅ ๋์ถ ๋ฐ ์์ฌ ๊ฒฐ์ ์ต์ ํ
โ 1.2 Istio์์ A/B ํ ์คํธ ํ์ฉ ๋ฐฉ์
Istio์์๋ VirtualService์ DestinationRule์ ํ์ฉํ์ฌ A/B ํ ์คํธ๋ฅผ ์ฝ๊ฒ ๊ตฌํํ ์ ์์ต๋๋ค.
- VirtualService → ์์ฒญ ํค๋ ๊ฐ(์: User-Agent, Cookie)์ ๊ธฐ๋ฐ์ผ๋ก ํน์ ๋ฒ์ ์ผ๋ก ํธ๋ํฝ ๋ผ์ฐํ
- DestinationRule → A/B ๊ทธ๋ฃน์ ๊ฐ๊ฐ์ ์๋น์ค ์๋ธ์ (Subset)์ผ๋ก ์ค์
๐น 2. Istio A/B ํ ์คํธ ๊ตฌํ
โ 2.1 A/B ํ ์คํธ๋ฅผ ์ํ DestinationRule ์ค์
์๋ DestinationRule ์์ ์์๋ ์๋น์ค ๋ฒ์ (v1, v2)์ ์๋ธ์ (Subset)์ผ๋ก ๊ตฌ๋ถํฉ๋๋ค.
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: my-service
spec:
host: my-service # ๋์ ์๋น์ค ์ด๋ฆ
subsets:
- name: v1 # A ๊ทธ๋ฃน(๊ธฐ์กด ๋ฒ์ )
labels:
version: v1
- name: v2 # B ๊ทธ๋ฃน(์๋ก์ด ๋ฒ์ )
labels:
version: v2
trafficPolicy:
loadBalancer:
simple: ROUND_ROBIN # ๋ผ์ด๋ ๋ก๋น ๋ฐฉ์์ผ๋ก ํธ๋ํฝ ๋ถ๋ฐฐ
์ค๋ช :
- subsets.name: v1 → ๊ธฐ์กด ์๋น์ค(A ๊ทธ๋ฃน)
- subsets.name: v2 → ์๋ก์ด ์๋น์ค(B ๊ทธ๋ฃน)
- trafficPolicy.loadBalancer.simple: ROUND_ROBIN → ๋ผ์ด๋ ๋ก๋น ๋ฐฉ์ ๋ก๋ ๋ฐธ๋ฐ์ฑ ์ ์ฉ
โ 2.2 VirtualService๋ฅผ ํ์ฉํ A/B ํ ์คํธ ํธ๋ํฝ ๋ผ์ฐํ
์๋ VirtualService๋ ํน์ ์ฟ ํค ๊ฐ(user=group-b)์ ๊ธฐ๋ฐ์ผ๋ก ํธ๋ํฝ์ v2 ์๋น์ค๋ก ๋ผ์ฐํ ํฉ๋๋ค.
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: ab-testing
spec:
hosts:
- my-service
http:
- match:
- headers:
cookie:
regex: ".*user=group-b.*" # ํน์ ์ฟ ํค๋ฅผ ๊ฐ์ง ์ฌ์ฉ์๋ง v2 ์๋น์ค๋ก ๋ผ์ฐํ
route:
- destination:
host: my-service
subset: v2 # ์๋ก์ด ๋ฒ์ (B ๊ทธ๋ฃน)์ผ๋ก ํธ๋ํฝ ์ ๋ฌ
- route:
- destination:
host: my-service
subset: v1 # ๊ธฐ๋ณธ์ ์ผ๋ก ๊ธฐ์กด ๋ฒ์ (A ๊ทธ๋ฃน)์ผ๋ก ํธ๋ํฝ ์ ๋ฌ
์ค๋ช :
- match.headers.cookie.regex: ".*user=group-b.*"
→ user=group-b ์ฟ ํค๋ฅผ ํฌํจํ ์ฌ์ฉ์๋ง v2๋ก ๋ผ์ฐํ - route.destination.subset: v1 → ๊ธฐ๋ณธ์ ์ผ๋ก ๊ธฐ์กด ์๋น์ค(A ๊ทธ๋ฃน)๋ก ํธ๋ํฝ ์ ๋ฌ
๐น 3. A/B ํ ์คํธ ์ ์ฉ ์๋๋ฆฌ์ค
1๏ธโฃ ๊ธฐ๋ณธ ์ํ:
- ๋ชจ๋ ์ฌ์ฉ์๋ ๊ธฐ์กด ์๋น์ค(v1)๋ก ์ ์
2๏ธโฃ A/B ๊ทธ๋ฃน ์ค์ :
- user=group-b ์ฟ ํค๊ฐ ์ค์ ๋ ์ฌ์ฉ์๋ v2(์๋ก์ด ๋ฒ์ )๋ก ์ ์
3๏ธโฃ ํ ์คํธ ์งํ:
- A/B ๊ทธ๋ฃน ์ฌ์ฉ์๋ค์ ๋ฐ์, ์ฑ๋ฅ, ์ค๋ฅ์จ ๋ฑ์ ๋ถ์
4๏ธโฃ ๊ฒฐ๊ณผ ๋ถ์ ํ ์ต์ข ๊ฒฐ์ :
- v2 ์๋น์ค๊ฐ ์์ ์ ์ด๊ณ ํจ๊ณผ๊ฐ ๋๋ค๋ฉด, ์ ์ฒด ํธ๋ํฝ์ v2๋ก ์ด๋
- ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ฉด ์ฆ์ v1์ผ๋ก ๋กค๋ฐฑ ๊ฐ๋ฅ
๐ ๊ฒฐ๋ก
- A/B ํ ์คํธ๋ ํน์ ์ฌ์ฉ์ ๊ทธ๋ฃน์ ๋์์ผ๋ก ์๋ก์ด ๊ธฐ๋ฅ์ ์ ๊ณตํ๊ณ , ํจ๊ณผ๋ฅผ ๋น๊ต ๋ถ์ํ๋ ๊ธฐ๋ฒ์ ๋๋ค.
- Istio์ VirtualService์ DestinationRule์ ํ์ฉํ๋ฉด A/B ํ ์คํธ๋ฅผ ์์ฝ๊ฒ ๊ตฌํ ๊ฐ๋ฅํฉ๋๋ค.
- ํค๋ ๊ฐ(์ฟ ํค, User-Agent ๋ฑ)์ ๊ธฐ๋ฐ์ผ๋ก ํน์ ์ฌ์ฉ์๋ง ์๋ก์ด ๋ฒ์ ์ ์๋น์ค๋ก ๋ผ์ฐํ ํ ์ ์์ต๋๋ค.
- ํ ์คํธ ๊ฒฐ๊ณผ๋ฅผ ๋ถ์ํ ํ, ์ ๊ท ์๋น์ค(v2)๋ก ์ ์ฒด ํธ๋ํฝ์ ์ด๋ํ๊ฑฐ๋, ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ฉด ๊ธฐ์กด ์๋น์ค(v1)๋ก ๋กค๋ฐฑํ ์ ์์ต๋๋ค.