MSA(Microservice Architecture)
MSA(Microservice Architecture)๋ ํ๋์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฌ๋ฌ ๊ฐ์ ๋ ๋ฆฝ์ ์ธ ์๋น์ค๋ก ๋ถ๋ฆฌํ์ฌ ๊ฐ๋ฐ, ๋ฐฐํฌ, ์ ์ง๋ณด์๋ฅผ ์ฉ์ดํ๊ฒ ํ๋ ์ํํธ์จ์ด ์ํคํ ์ฒ ์คํ์ผ
- ๊ฐ ์๋น์ค๋ ํน์ ๋น์ฆ๋์ค ๊ธฐ๋ฅ์ ์ํํ๋ฉฐ, ์๋ก ๋ ๋ฆฝ์ ์ผ๋ก ๋ฐฐํฌ๋๊ณ ํ์ฅ๋ ์ ์์
- ์๋น์ค ๊ฐ์ ํต์ ์ ์ฃผ๋ก HTTP/HTTPS, ๋ฉ์์ง ํ ๋ฑ์ ํตํด ์ด๋ฃจ์ด์ง
์ฃผ์ ํน์ง
- ๋ ๋ฆฝ์ ์ธ ๋ฐฐํฌ ๊ฐ๋ฅ์ฑ: ๊ฐ ์๋น์ค๋ ๋ ๋ฆฝ์ ์ผ๋ก ๋ฐฐํฌํ ์ ์์ผ๋ฉฐ, ๋ค๋ฅธ ์๋น์ค์ ์ํฅ์ ์ฃผ์ง ์๊ณ ์ ๋ฐ์ดํธ ๊ฐ๋ฅ
- ์์ ํ ๊ตฌ์ฑ: ๊ฐ ์๋น์ค๋ ์์ ํ์ด ๋ ๋ฆฝ์ ์ผ๋ก ๊ฐ๋ฐํ๊ณ ๊ด๋ฆฌํ ์ ์์
- ๊ธฐ์ ์คํ์ ๋ค์์ฑ: ๊ฐ ์๋น์ค๋ ์ ์ ํ ๊ธฐ์ ์คํ์ ์์ ๋กญ๊ฒ ์ ํ ๊ฐ๋ฅ
๋ชจ๋๋ฆฌํฑ ์ํคํ ์ฒ์์ ๋น๊ต
๋ชจ๋๋ฆฌํฑ ์ํคํ ์ฒ
๋ชจ๋๋ฆฌํฑ ์ํคํ ์ฒ๋ ํ๋์ ํฐ ์ฝ๋๋ฒ ์ด์ค๋ก ๊ตฌ์ฑ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ผ๋ก, ๋ชจ๋ ๊ธฐ๋ฅ์ด ํ๋์ ์ ํ๋ฆฌ์ผ์ด์ ๋ด์ ํฌํจ๋์ด ์์

์ฅ์
- ๊ฐ๋จํ ๋ฐฐํฌ: ๋ชจ๋ ์ฝ๋๊ฐ ํ๋์ ์ฝ๋๋ฒ ์ด์ค์ ํฌํจ๋์ด ์์ด ๋ฐฐํฌ๊ฐ ๋จ์
- ๋จ์ผ ๋ฐ์ดํฐ๋ฒ ์ด์ค: ํ๋์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ฌ์ฉํด ๋ฐ์ดํฐ ์ผ๊ด์ฑ์ ์ฝ๊ฒ ์ ์ง ๊ฐ๋ฅ
๋จ์
- ํ์ฅ์ฑ ๋ถ์กฑ: ํน์ ๊ธฐ๋ฅ์ ํ์ฅํ๋ ค๋ฉด ์ ์ฒด ์ ํ๋ฆฌ์ผ์ด์ ์ ํ์ฅํด์ผํจ
- ๊ธด ๊ฐ๋ฐ ์ฃผ๊ธฐ: ์์ ๋ณ๊ฒฝ ์ฌํญ๋ ์ ์ฒด ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ค์ ๋ฐฐํฌํด์ผํจ
- ์ ์ฐ์ฑ ๋ถ์กฑ: ์๋ก์ด ๊ธฐ์ ๋์ ์ด ์ด๋ ต๊ณ , ํน์ ๋ชจ๋์ ์ข ์์ ์
MSA
MSA๋ ์ฌ๋ฌ ๊ฐ์ ๋ ๋ฆฝ์ ์ธ ์๋น์ค๋ก ๊ตฌ์ฑ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ผ๋ก, ๊ฐ ์๋น์ค๋ ํน์ ๋น์ฆ๋์ค ๊ธฐ๋ฅ์ ์ํํจ

์ฅ์
- ํ์ฅ์ฑ: ํน์ ์๋น์ค๋ง ํ์ฅํ์ฌ ์ฑ๋ฅ ์ต์ ํํ ์ ์์
- ๋ ๋ฆฝ์ ๋ฐฐํฌ: ๊ฐ๋ณ ์๋น์ค์ ๋ณ๊ฒฝ ์ฌํญ์ ๋ ๋ฆฝ์ ์ผ๋ก ๋ฐฐํฌ ๊ฐ๋ฅ
- ์ ์ฐ์ฑ: ์๋น์ค๋ณ๋ก ์ ํฉํ ๊ธฐ์ ์คํ ์ ํ ๊ฐ๋ฅ
- ์์ ํ ๊ตฌ์ฑ: ์๋น์ค๋ณ ์์ ํ์ผ๋ก ๊ตฌ์ฑ๋์ด ๋ฏผ์ฒฉํ ๊ฐ๋ฐ ๊ฐ๋ฅ
๋จ์
- ๋ณต์ก์ฑ ์ฆ๊ฐ: ์๋น์ค ๊ฐ ํต์ , ๋ฐ์ดํฐ ์ผ๊ด์ฑ ์ ์ง ๋ฑ์ ๋ณต์ก์ฑ์ด ์ฆ๊ฐ
ex) ์ฃผ๋ฌธ ์๋น์ค์์ ์ํ์ด ์กด์ฌํ๋์ง ํ์ธ์ ์ํด ์ํ ์๋น์ค๋ฅผ ํธ์ถํด์ผ ํ๊ณ , ๋๊ฐ ์ฃผ๋ฌธํ๋์ง ํ์ธํ๊ธฐ ์ํด ํ์ ์๋น์ค๋ฅผ ํธ์ถํด์ผํจ
- ์ฆ, ํ๋์ ์๋น์ค๊ฐ ๋ค๋ฅธ ์๋น์ค๋ฅผ ํธ์ถํด์ผ ํ๊ธฐ ๋๋ฌธ์ ๋ณต์กํจ
ex) ์ฃผ๋ฌธ ์๋น์ค์์ ์ฌ์ฉํ๋ DB์ ํ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ ์ ์ฅํ๋ ๊ฒฝ์ฐ, ์ถํ ํ์ ํํด์ ๊ฐ์ ๋ณ๊ฒฝ์ด ์ด๋ฃจ์ด์ก์ ๋ ๋ฐ์ดํฐ ์ผ๊ด์ฑ์ด ๋ง์ง ์๊ฒ๋จ - ์ด์ ๋น์ฉ ์ฆ๊ฐ: ๊ฐ ์๋น์ค์ ๋ชจ๋ํฐ๋ง, ๋ก๊น ๋ฑ์ ๊ฐ๋ณ์ ์ผ๋ก ๊ด๋ฆฌํด์ผ ํจ
- ๋คํธ์ํฌ ์ง์ฐ: ์๋น์ค ๊ฐ์ ํต์ ์ด ๋คํธ์ํฌ๋ฅผ ํตํด ์ด๋ฃจ์ด์ง๋ฏ๋ก ์ง์ฐ ์๊ฐ์ด ๋ฐ์ํ ์ ์์
๏นก์ฅ๋จ์ ์ ์ ํ์ ํด์ ์ ํฉํ ์ํคํ ์ฒ ๋ฐฉ์์ ์ ํํ๋ ๊ฒ์ด ์ค์ํจ
๋ณดํต ์ ๊ท ํ๋ก์ ํธ๋ ๋น ๋ฅธ ๋๋ฉ์ด ํ์ํ๊ธฐ ๋๋ฌธ์ ๋ชจ๋๋ฆฌํฑ์ผ๋ก ๊ฐ๋ฐํ๋๊ฒ ์ข๊ณ , ์ ์ ์๋น์ค ์ฌ์ด์ฆ๊ฐ ์ปค์ก์ ๋ MSA ์ ํํ๋ ๋ฐฉํฅ์ผ๋ก ๊ฐ๋ ์ข์
Spring Cloud
Spring Cloud๋ ๋ง์ดํฌ๋ก์๋น์ค ๊ฐ๋ฐ์ ์ํด ๋ค์ํ ๋๊ตฌ์ ์๋น์ค๋ฅผ ์ ๊ณตํ๋ ์คํ๋ง ํ๋ ์์ํฌ์ ํ์ฅ์ผ๋ก, ๋ง์ดํฌ๋ก์๋น์ค ์ํคํ ์ฒ๋ฅผ ์ฝ๊ฒ ๊ตฌํํ๊ณ ์ด์ํ ์ ์๋๋ก ๋์
์ฃผ์ ๊ธฐ๋ฅ
- ์๋น์ค ๋ฑ๋ก ๋ฐ ๋์ค์ปค๋ฒ๋ฆฌ: Eureka, Consul, Zookeeper
- ๋ก๋ ๋ฐธ๋ฐ์ฑ: Ribbon, Spring Cloud LoadBalancer
- ์ํท ๋ธ๋ ์ด์ปค: Hystrix, Resilience4j
- API ๊ฒ์ดํธ์จ์ด: Zuul, Spring Cloud Gateway
- ๊ตฌ์ฑ ๊ด๋ฆฌ: Spring Cloud Config
- ๋ถ์ฐ ์ถ์ : Spring Cloud Sleuth, Zipkin
- ๋ฉ์์ง: Spring Cloud Stream
์ค์ ๋ก ํ์ ์์๋ ์๋ง Hystrix๋ Zuul๋ก ๊ฐ๋ฐ๋ ์ํ๊ฐ ๋ง์.
Spring์ ๊ณ์ํด์ ๋ฒ์ ์ด ์ ๋ฐ์ดํธ๋๊ธฐ ๋๋ฌธ์ ํ์ฌ Spring ๋ฒ์ ์ ๋ง๋ ๊ฐ๋ฐ์ ์งํํด์ผ ํ์ง๋ง, ์ค์ ํ์ ์์๋ ๋ฒ์ ์ ์ฌ๋ฆด ๋ ๊ณ ๋ ค ์ฌํญ์ด ๋ง์ด ๋๋ฌธ์ ์ด๋ ค์์ด ์กด์ฌํด์ ๋ฒ์ ์ ์ ์ฝ๊ฒ ํ ์ ์์
Spring Cloud ์ฃผ์ ๋ชจ๋
์๋น์ค ๋ฑ๋ก ๋ฐ ๋์ค์ปค๋ฒ๋ฆฌ
1) Eureka(์ ๋ ์นด)
๋ทํ๋ฆญ์ค๊ฐ ๊ฐ๋ฐํ ์๋น์ค ๋์ค์ปค๋ฒ๋ฆฌ ์๋ฒ๋ก, ๋ง์ดํฌ๋ก์๋น์ค ์ํคํ ์ฒ์์ ๊ฐ ์๋น์ค์ ์์น๋ฅผ ๋์ ์ผ๋ก ๊ด๋ฆฌ
- ์๋น์ค ๋ ์ง์คํธ๋ฆฌ: ๋ชจ๋ ์๋น์ค ์ธ์คํด์ค์ ์์น๋ฅผ ์ ์ฅํ๋ ์ค์ ์ ์ฅ์
- ํฌ์ค ์ฒดํฌ(Health check): ์๋น์ค ์ธ์คํด์ค์ ์ํ๋ฅผ ์ฃผ๊ธฐ์ ์ผ๋ก ํ์ธํด ๊ฐ์ฉ์ฑ์ ๋ณด์ฅ
๋ก๋ ๋ฐธ๋ฐ์ฑ
1) Ribbon
๋ทํ๋ฆญ์ค๊ฐ ๊ฐ๋ฐํ ํด๋ผ์ด์ธํธ ์ฌ์ด๋ ๋ก๋ ๋ฐธ๋ฐ์๋ก, ์๋น์ค ์ธ์คํด์ค ๊ฐ์ ๋ถํ๋ฅผ ๋ถ์ฐ
- ์๋ฒ ๋ฆฌ์คํธ ์ ๊ณต์: Eureka๋ก๋ถํฐ ์๋น์ค ์ธ์คํด์ค ๋ฆฌ์คํธ๋ฅผ ์ ๊ณต๋ฐ์ ๋ก๋ ๋ฐธ๋ฐ์ฑ์ ์ฌ์ฉ
- ๋ก๋ ๋ฐธ๋ฐ์ฑ ์๊ณ ๋ฆฌ์ฆ: ๋ผ์ด๋ ๋ก๋น, ๊ฐ์ค์น ๊ธฐ๋ฐ ๋ฑ ๋ค์ํ ๋ก๋ ๋ฐธ๋ฐ์ฑ ์๊ณ ๋ฆฌ์ฆ ์ง์
- Failover: ์์ฒญ ์คํจ ์ ๋ค๋ฅธ ์ธ์คํด์ค๋ก ์๋ ์ ํ
์ํท ๋ธ๋ ์ด์ปค(Circuit Breaker)
1) Hystrix
๋ทํ๋ฆญ์ค๊ฐ ๊ฐ๋ฐํ ์ํท ๋ธ๋ ์ด์ปค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก, ์๋น์ค ๊ฐ์ ํธ์ถ ์คํจ๋ฅผ ๊ฐ์งํ๊ณ ์์คํ ์ ์ ์ฒด์ ์ธ ์์ ์ฑ์ ์ ์ง
- ์ํท ๋ธ๋ ์ด์ปค ์ํ: ํด๋ก์ฆ๋, ์คํ, ํ๋-์คํ ์ํ๋ฅผ ํตํด ํธ์ถ ์คํจ๋ฅผ ๊ด๋ฆฌ
- Failback: ํธ์ถ ์คํจ ์ ๋์ฒด ๋ก์ง์ ์ ๊ณตํด ์์คํ ์์ ์ฑ ํ๋ณด
- ๋ชจ๋ํฐ๋ง: Hystrix Dashboard๋ฅผ ํตํด ์ํท ๋ธ๋ ์ด์ปค ์ํ ๋ชจ๋ํฐ๋ง
2) Resilience4j
์๋ฐ ๊ธฐ๋ฐ์ ๊ฒฝ๋ ์ํท ๋ธ๋ ์ด์ปค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก, ๋ทํ๋ฆญ์ค Hystrix์ ๋์์ผ๋ก ๊ฐ๋ฐ๋จ
- ์ํท ๋ธ๋ ์ด์ปค: ํธ์ถ ์คํจ๋ฅผ ๊ฐ์งํ๊ณ ์ํท์ ์ด์ด ์ถ๊ฐ์ ์ธ ํธ์ถ์ ์ฐจ๋จํด ์์คํ ์ ๋ถํ๋ฅผ ์ค์
- Fallback: ํธ์ถ ์คํจ ์ ๋์ฒด ๋ก์ง์ ์คํํด ์์คํ ์ ์์ ์ฑ ์ ์ง
- ํ์์์ ์ค์ : ํธ์ถ์ ์๋ต ์๊ฐ์ ์ค์ ํด ๋๋ฆฐ ์๋น์ค ํธ์ถ์ ๋์
- ์ฌ์๋: ์ฌ์๋ ๊ธฐ๋ฅ์ ์ง์ํด ์ผ์์ ์ธ ๋คํธ์ํฌ ๋ฌธ์ ๋ฑ์ ๋์
Spring Cloud ๊ตฌ์ฑ ์์์ ํ์ฉ
API ๊ฒ์ดํธ์จ์ด
1) Zuul
๋ทํ๋ฆญ์ค๊ฐ ๊ฐ๋ฐํ API ๊ฒ์ดํธ์จ์ด๋ก, ๋ชจ๋ ์๋น์ค ์์ฒญ์ ์ค์์์ ๊ด๋ฆฌ
- ๋ผ์ฐํ : ์์ฒญ URL์ ๋ฐ๋ผ ์ ์ ํ ์๋น์ค๋ก ์์ฒญ ์ ๋ฌ
- ํํฐ: ์์ฒญ ์ ํ์ ๋ค์ํ ์์ ์ ์ํํ ์ ์๋ ํํฐ ์ฒด์ธ ์ ๊ณต
- ๋ชจ๋ํฐ๋ง: ์์ฒญ ๋ก๊ทธ ๋ฐ ๋งคํธ๋ฆญ์ ํตํด ์๋น์ค ์ํ ๋ชจ๋ํฐ๋ง ๊ฐ๋ฅ
2) Cloud gateway
์คํ๋ง ํด๋ผ์ฐ๋์์ ์ ๊ณตํ๋ API ๊ฒ์ดํธ์จ์ด๋ก, ๋ง์ดํฌ๋ก์๋น์ค ์ํคํ ์ฒ์์ ํ์์ ์ธ ์ญํ
- ๋ฃจํ ๋ฐ ํํฐ๋ง: ์์ฒญ์ ๋ฐ์ ํน์ ์๋น์ค๋ก ๋ผ์ฐํ ํ๊ณ ํ์ํ ์ธ์ฆ ๋ฐ ๊ถํ ๋ถ์ฌ ์ํ
- ๋ณด์: ์ธ๋ถ ์์ฒญ์ผ๋ก๋ถํฐ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ณดํธํ๊ณ , ๋ณด์ ์ ์ฑ ์ ์ฉ
- ํจ์จ์ฑ: ๋ง์ดํฌ๋ก์๋น์ค ์ํคํ ์ฒ์์ ํ์ํ ์์ฒญ ์ฒ๋ฆฌ ๋ฐ ๋ถ์ฐ ํ๊ฒฝ์ ๊ด๋ฆฌ๋ฅผ ํจ์จ์ ์ผ๋ก ์ํ
๊ตฌ์ฑ ๊ด๋ฆฌ
1) Spring Cloud Config
๋ถ์ฐ๋ ํ๊ฒฝ์์ ์ค์ ์ง์ค์ ์ค์ ๊ด๋ฆฌ๋ฅผ ์ ๊ณต
- Config ์๋ฒ: ์ค์์์ ์ค์ ํ์ผ์ ๊ด๋ฆฌํ๊ณ ๊ฐ ์๋น์ค์ ์ ๊ณต
- Config ํด๋ผ์ฐ๋: Config ์๋ฒ์์ ์ค์ ์ ๋ฐ์์ ์ฌ์ฉํ๋ ์๋น์ค
- ์ค์ ๊ฐฑ์ : ์ค์ ๋ณ๊ฒฝ ์ ์๋น์ค ์ฌ์์ ์์ด ์ค์๊ฐ์ผ๋ก ๋ฐ์
Spring Cloud์ ์ ์ฉ ์ฌ๋ก
Netflix๋ Spring Cloud๋ฅผ ํ์ฉํด์ ๋ง์ดํฌ๋ก์๋น์ค ์ํคํ ์ฒ๋ก ์ ํํจ
- ์๋น์ค ๋ถ๋ฆฌ: ๊ธฐ์กด ๋ชจ๋๋ฆฌ์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฌ๋ฌ ๊ฐ์ ๋ ๋ฆฝ์ ์ธ ๋ง์ดํฌ๋ก์๋น์ค๋ก ๋ถ๋ฆฌ
- ์๋ํ ๋๊ตฌ ๋์ : CI/CD ํ์ดํ๋ผ์ธ์ ๊ตฌ์ถํด ์ฝ๋์ ๋น๋, ํ ์คํธ, ๋ฐฐํฌ ๊ณผ์ ์๋ํํด ๊ฐ๋ฐ ์๋ ํฅ์
- ์์ฒด ๋๊ตฌ ๊ฐ๋ฐ: Hystrix, Eureka, Ribbon ๋ฑ์ ๋๊ตฌ๋ฅผ ๊ฐ๋ฐํด ์๋น์ค ๊ฐ ํต์ , ์ฅ์ ๋ณต๊ตฌ, ๋ก๋ ๋ฐธ๋ฐ์ฑ ๋ฑ์ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌ
- ํด๋ผ์ฐ๋ ์ธํ๋ผ ํ์ฉ: AWS์ ๊ฐ์ ํด๋ผ์ฐ๋ ์ธํ๋ผ๋ฅผ ํ์ฉํด ์๋น์ค ํ์ฅ์ฑ ๋์
MSA๋ฅผ ํ์ฉํ ์ ํ๋ฆฌ์ผ์ด์ ์ค๊ณ
์๊ตฌ์ฌํญ ๋ฐ ๋ก์ง
- ์๊ตฌ์ฌํญ: ์ฃผ๋ฌธ์ด ๋ค์ด์ค๋ฉด ์ฃผ๋ฌธ ํ์ธํ๊ณ ์ฒ๋ฆฌ
- ํ๋ฆ: ์ฃผ๋ฌธ ํ์ธ → ์ํ ํ์ธ → ์ ์ ํ์ธ
- ์ฆ, ์ด 3๊ฐ์ ์ ํ๋ฆฌ์ผ์ด์ ํ์ํจ! Order ์ ํ๋ฆฌ์ผ์ด์ , Product ์ ํ๋ฆฌ์ผ์ด์ , User ์ ํ๋ฆฌ์ผ์ด์

1. Ribbon์ ํตํ ์๋น์ค ๊ฐ์ ํต์

๊ฐ ์ ํ๋ฆฌ์ผ์ด์ ์ด ํ๋๋ง ์กด์ฌํ๋ ๊ฒ์ด ์๋๋ผ ์ฌ๋ฌ ๊ฐ๊ฐ ์กด์ฌํ๋ ๊ฒฝ์ฐ,
ex) Order ์ ํ๋ฆฌ์ผ์ด์ ์์ Product ์ ํ๋ฆฌ์ผ์ด์ ์ผ๋ก ์์ฒญ ์ ๋ ๊ฐ์ Product ์ ํ๋ฆฌ์ผ์ด์ ์๋น์ค ์ค ์ด๋๋ก ํธ์ถํด์ผ ํ ์ง Ribbon์์ ํด์ฃผ๊ณ ์ค์ ํธ์ถ์ Feign Client๋ฅผ ํตํด์ ์ด๋ฃจ์ด์ง
2. Eureka ์๋ฒ ๊ตฌ์ถ
ํน์ ์ ํ๋ฆฌ์ผ์ด์ ์ ํธ์ถํ ๋ ์์น๋ฅผ ์์์ผํ๊ธฐ ๋๋ฌธ์ Eureka ์๋ฒ ๊ตฌ์ถ ํ, ๊ฐ ์ ํ๋ฆฌ์ผ์ด์ ์๋ฒ ๋ฑ๋ก

ex) Order ์ ํ๋ฆฌ์ผ์ด์ ์์ Product ์ ํ๋ฆฌ์ผ์ด์ ์ ํธ์ถํ ์ง User๋ฅผ ํธ์ถํ ์ง Host๋ฅผ ์์์ผํจ
์ฆ Order ์๋ฒ์์ Product ์๋ฒ ํธ์ถํ ๋, Eureka ์๋ฒ์ ๋ค์ด๊ฐ์ ์ ๋ณด ํ์ธํ๋ฉด ๋๊ตด ํธ์ถํ ์ง ์์๋ผ ์ ์์
- Eureka ์๋ฒ์ ๋ฑ๋ก๋ ๋ชจ๋ ์๋น์ค ์ฑ๋ค์ Eureka ํด๋ผ์ด์ธํธ๊ฐ ๋จ
3. API ๊ฒ์ดํธ์จ์ด
์ด๋ค ์ฌ์ฉ์๊ฐ ์ฃผ๋ฌธ์ ํ์ธํ ๋๋ Order ์ ํ๋ฆฌ์ผ์ด์ ์ ํธ์ถํ๊ณ , ์ํ ๋ฑ๋กํ ๋๋ Product ์ ํ๋ฆฌ์ผ์ด์ ํธ์ถํ๋ ๋ฑ Order, Product, User๋ฅผ ํธ์ถํ ๋๋ง๋ค ๋ค๋ฅธ ํธ์คํธ๋ก ํธ์ถํด์ผํ๋ ๋ฒ๊ฑฐ๋ก์ ์กด์ฌํจ
์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์๋จ์ API ๊ฒ์ดํธ์จ์ด๋ฅผ ๋ฌ์ ๊ฐ ์์ฒญ์ ๋ง๋ ์๋ฒ๋ก ์์ฒญ์ ๋ณด๋
๋ํ, ๋ก๊ทธ์ธ๋ ํ์๋ง ์ฌ์ฉ๊ฐ๋ฅํ๊ฒ ํ๊ธฐ ์ํด์ API ๊ฒ์ดํธ์จ์ด๊ฐ ๋ก๊ทธ์ธ์ ๋ํด ์ฒดํฌํ๋ฉด, ๋ถํ์ํ ์ ๋ณด๋ฅผ ๋ค๋ก ์ ๋ฌํ์ง ์๊ณ Block ์์ผ ์์ฒญ์ ๋๋๋ฆด ์ ์์ → API ๊ฒ์ดํธ์จ์ด์ Filter์์ ๋ก๊ทธ์ธ ์ฒดํฌ ์ํ

4. Resilience4j ์ํท ๋ธ๋ ์ด์ปค๋ฅผ ํตํ Failover ์ฒ๋ฆฌ
Order ์๋ฒ๊ฐ User ์๋ฒ๋ฅผ ํธ์ถํ ํ Product ์๋ฒ๋ฅผ ํธ์ถํ๋ ํ๋ฆ์ธ ์ํฉ์์ User ์๋ฒ ํธ์ถ์ ์๋ฌ๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ, ์ํท ๋ธ๋ ์ด์ปค์ธ Resilience4j๋ฅผ ํตํด Product ์๋ฒ ํธ์ถ๊น์ง ์ํํ์ง ์๊ณ ์๋ฌ๊ฐ ๋ฐ์ํ User ์๋ฒ์์ Block ์ํค๊ธฐ

5. yml ํ์ผ์ ์ ์ฅํ๋ Config ์๋ฒ ์ถ๊ฐ
์ ํ๋ฆฌ์ผ์ด์ ๋ง๋ค ๊ฐ๊ฐ์ yml ํ์ผ์ ๊ฐ๊ฐ ๋ฃ์ด์ฃผ๋ฉด, ์์ ์ด ํ์ํ ๋ ์ ๋ถ ์์ ํด ์ค์ผํจ. ์ด๋ฌํ ๊ฒ์ ๋ฐฉ์งํ๊ธฐ ์ํด yml ํ์ผ์ ์ ์ฅํ๋ Config ์๋ฒ ์ถ๊ฐ
- yml ํ์ผ์ด ์์ ๋์์ ๋, ๊ฐ ์๋น์ค์ ์๋ ค์ฃผ๋ฉด ๋ชจ๋ ์๋น์ค๊ฐ ๋ฐ๋ก ์ ๋ฐ์ดํธ๋จ

6. ๋ถ์ฐ ์ถ์ Zipkin์ผ๋ก ์์ฒญ ํธ์ถ ํ์
์ฃผ๋ฌธ → ์ํ → ํ์ ์์ผ๋ก ํธ์ถ์ด ์งํ๋ ๋, ๊ฐ ์ฑ๋ค์ด ํธ์ถ๋๋์ง ์ฝ๊ฒ ์ ์ ์์! ๋ถ์ฐ ์ถ์ Zipkin์ ํตํด์ ์์ฒญ์ด ๋ค์ด์์ ๋ ์ด๋ป๊ฒ ํธ์ถ๋๋์ง ํ์

์๋น์ค ๋์ค์ปค๋ฒ๋ฆฌ
์๋น์ค ๋์ค์ปค๋ฒ๋ฆฌ๋?
์๋น์ค ๋์ค์ปค๋ฒ๋ฆฌ๋ ๋ง์ดํฌ๋ก์๋น์ค ์ํคํ ์ฒ์์ ๊ฐ ์๋น์ค์ ์์น๋ฅผ ๋์ ์ผ๋ก ๊ด๋ฆฌํ๊ณ ์ฐพ์์ฃผ๋ ๊ธฐ๋ฅ
- ๊ฐ ์๋น์ค๋ ๋ฑ๋ก ์๋ฒ์ ์์ ์ ์์น๋ฅผ ๋ฑ๋กํ๊ณ , ๋ค๋ฅธ ์๋น์ค๋ ์ด๋ฅผ ์กฐํํด ํต์ ํจ
- ์ฃผ์ ๊ธฐ๋ฅ์ผ๋ก๋ ์๋น์ค ๋ฑ๋ก, ์๋น์ค ์กฐํ, ํฌ์ค ์ฒดํฌ ๋ฑ์ด ์์
Eureka๋?
Eureka๋ ๋ทํ๋ฆญ์ค๊ฐ ๊ฐ๋ฐํ ์๋น์ค ๋์ค์ปค๋ฒ๋ฆฌ ์๋ฒ๋ก, ๋ง์ดํฌ๋ก์๋น์ค ์ํคํ ์ฒ์์ ๊ฐ ์๋น์ค์ ์์น๋ฅผ ๋์ ์ผ๋ก ๊ด๋ฆฌ
- ๋ชจ๋ ์๋น์ค ์ธ์คํด์ค์ ์์น๋ฅผ ์ ์ฅํ๋ ์ค์ ์ ์ฅ์ ์ญํ ์ ํ๋ฉฐ, ์๋น์ค ์ธ์คํด์ค์ ์ํ๋ฅผ ์ฃผ๊ธฐ์ ์ผ๋ก ํ์ธํด ๊ฐ์ฉ์ฑ์ ๋ณด์ฅํจ
- ์ฌ๋ฌ ์ธ์คํด์ค๋ฅผ ์ง์ํ์ฌ ๊ณ ๊ฐ์ฉ์ฑ์ ์ ์งํ ์ ์์
1. Eureka ์๋ฒ ์ค์
- Eureka ์๋ฒ๋ ๋ ์ง์คํธ๋ฆฌ๋ฅผ ๊ตฌ์ฑํ๋ ์ค์ ์๋ฒ๋ก, application.properties ํน์ application.yml ํ์ผ์์ ์งํ
server:
port: 8761
eureka:
client:
register-with-eureka: false # ๋ค๋ฅธ Eureka ์๋ฒ์ ์ด ์๋ฒ๋ฅผ ๋ฑ๋กํ์ง ์์
fetch-registry: false # ๋ค๋ฅธ Eureka ์๋ฒ์ ๋ ์ง์คํธ๋ฆฌ๋ฅผ ๊ฐ์ ธ์ค์ง ์์
server:
enable-self-preservation: false # ์๊ธฐ ๋ณดํธ ๋ชจ๋ ๋นํ์ฑํ
2. Eureka ํด๋ผ์ด์ธํธ ์ค์
- ๊ฐ ์๋น์ค๋ Eureka ์๋ฒ์ ์์ ์ ๋ฑ๋กํด์ผ ํจ
- spring-cloud-starter-netflix-eureka-client ์์กด์ฑ์ ์ฌ์ฉํ๊ณ , ์ ํ๋ฆฌ์ผ์ด์ ์ด๋ฆ๋ง ์ค์ ํ์ผ์ ์์ผ๋ฉด ์๋์ผ๋ก Eureka์ ๋ฑ๋ก๋จ
spring:
application:
name: my-service # ๋ฐ๋์ properties๋ yml ํ์ผ์ spring:application:name์ด ์ง์ ๋์ด ์์ด์ผํจ!
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/ # Eureka ์๋ฒ URL
register-with-eureka: true # Eureka ์๋ฒ์ ๋ฑ๋ก
fetch-registry: true # Eureka ์๋ฒ๋ก๋ถํฐ ๋ ์ง์คํธ๋ฆฌ ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ
instance:
hostname: localhost # ํด๋ผ์ด์ธํธ ํธ์คํธ ์ด๋ฆ
prefer-ip-address: true # IP ์ฃผ์ ์ฌ์ฉ ์ ํธ
lease-renewal-interval-in-seconds: 30 # ๋ฆฌ์ค ๊ฐฑ์ ๊ฐ๊ฒฉ
lease-expiration-duration-in-seconds: 90 # ๋ฆฌ์ค ๋ง๋ฃ ๊ธฐ๊ฐ
์๋น์ค ๋ฑ๋ก ๋ฐ ๋์ค์ปค๋ฒ๋ฆฌ
1. ์๋น์ค ๋ฑ๋ก
- ๊ฐ ์๋น์ค ์ ํ๋ฆฌ์ผ์ด์ ์ Eureka ์๋ฒ์ ์์ ์ ์์น ๋ฑ๋กํด์ผํจ
- spring-cloud-starter-netflix-eureka-client ์์กด์ฑ์ ์ฌ์ฉํ๊ณ , ์ ํ๋ฆฌ์ผ์ด์ ์ด๋ฆ๋ง ์ค์ ํ์ผ์ ์์ผ๋ฉด Eureka์ ๋ฑ๋ก๋จ
2. ์๋น์ค ๋์ค์ปค๋ฒ๋ฆฌ
ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์ ์ Eureka ์๋ฒ์์ ํ์ํ ์๋น์ค์ ์์น๋ฅผ ์กฐํํจ
- RestTemplate์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ
- Spring Boot ์ ํ๋ฆฌ์ผ์ด์ ์์ @LoadBalanced ์ ๋ ธํ ์ด์ ์ ์ฌ์ฉํด RestTemplate์ ๋ก๋ ๋ฐธ๋ฐ์ฑ ๊ธฐ๋ฅ ์ถ๊ฐ
- FeignClient๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ
- Spring Boot ์ ํ๋ฆฌ์ผ์ด์ ์์ FeignClient๋ฅผ ์ฌ์ฉํด ๊ฐํธํ๊ฒ ์๋น์ค ํธ์ถ ์ํํจ
@SpringBootApplication
@EnableFeignClients
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
@FeignClient(name = "my-service")
public interface MyServiceClient {
@GetMapping("/api/data")
String getData();
}
@RestController
public class MyFeignClientController {
@Autowired
private MyServiceClient myServiceClient;
@GetMapping("/get-data-feign")
public String getDataWithFeignClient() {
return myServiceClient.getData();
}
}
3. ํฌ์ค ์ฒดํฌ ๋ฐ ์ฅ์ ์ฒ๋ฆฌ
- ํฌ์ค ์ฒดํฌ๋ Eureka ์๋ฒ๊ฐ ์ฃผ๊ธฐ์ ์ผ๋ก ์๋น์ค ์ธ์คํด์ค์ ์ํ๋ฅผ ํ์ธํ์ฌ ๊ฐ์ฉ์ฑ์ ์ ์งํจ
- ๊ธฐ๋ณธ ํฌ์ค ์ฒดํฌ ์ค๋ํฌ์ธํธ /actuator/health ์ฌ์ฉ
- ์ฅ์ ์ฒ๋ฆฌ๋ ์๋น์ค ์ฅ์ ์ Eureka ์๋ฒ๋ ํด๋น ์ธ์คํด์ค๋ฅผ ๋ ์ง์คํธ๋ฆฌ์์ ์ ๊ฑฐํด ๋ค๋ฅธ ์๋น์ค์ ์ ๊ทผ ์ฐจ๋จํจ
Eureka์ ๊ณ ๊ฐ์ฉ์ฑ ๊ตฌ์ฑ
ํด๋ฌ์คํฐ ๊ตฌ์ฑ
- Eureka ์๋ฒ์ ๊ณ ๊ฐ์ฉ์ฑ์ ์ํด ์ฌ๋ฌ ์ธ์คํด์ค๋ฅผ ๊ตฌ์ฑํ ์ ์์
- ๋ค์ค ์ธ์คํด์ค๋ฅผ ๊ตฌ์ฑํ์ฌ ๊ณ ๊ฐ์ฉ์ฑ์ ์ ์งํ๋ฉฐ, ๊ฐ ์ธ์คํด์ค๋ ์๋ก๋ฅผ ํผ์ด๋ก ๋ฑ๋กํ์ฌ ์ํธ ๋ฐฑ์
- Eureka ์๋ฒ๋ ๋ค์ค ์ธ์คํด์ค๋ก ๊ตฌ์ฑํ ๋ ๊ฐ ์๋ฒ์ ํผ์ด ์ค์ ์ ํตํด ์๋ก๋ฅผ ์ธ์ํ๊ณ ๋ฐฑ์ ํ ์ ์์
์ค์ต
์ ๋ ์นด ์๋ฒ ํ๋์ ๊ฐ์ ๊ธฐ๋ฅ์ ํ๋ ์ธ์คํด์ค 2๊ฐ ์ฐ๊ฒฐํด๋ณด๊ธฐ

1. Spring Initializr๋ฅผ ํตํด ์คํ๋ง ํ๋ก์ ํธ ์์ฑ




2. Eureka Server ์ค์
# application.properties ์ค์
spring.application.name=server
server.port=19090
# ์ ๋ ์นด ์๋ฒ์ ๋ณธ์ธ ์ ํ๋ฆฌ์ผ์ด์
์ ๋ฑ๋กํ ์ง ๊ฒฐ์
# ์๋ฒ์์๋ ํด๋ผ์ด์ธํธ๋ก ์ธ์๋์ง ์๊ฒ false๋ก ์ค์
eureka.client.register-with-eureka=false
# ์ ๋ ์นด ์๋ฒ๋ก๋ถํฐ ๋ ์ง์คํธ๋ฆฌ๋ฅผ ๊ฐ์ ธ์ฌ์ง ๊ฒฐ์ ํ๋ ๋ณ์
eureka.client.fetch-registry=false
eureka.instance.hostname=localhost
# ์ ๋ ์นด ํด๋ผ์ด์ธํธ๊ฐ ์ ๋ ์นด ์๋ฒ์ ํต์ ์ ํ ๋ ์ฌ์ฉํ๋ URL ์ง์
eureka.client.service-url.defaultZone=http://localhost:19090/eureka/
package com.spring_cloud.eureka.server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer // ์ถ๊ฐํด์ค์ผ์ง ์ ๋ ์นด ์๋ฒ๋ก ๋์ํจ
public class ServerApplication {
public static void main(String[] args) {
SpringApplication.run(ServerApplication.class, args);
}
}
3. Eureka Client ์ค์ (๋ ๊ณณ ๋ชจ๋ ์ถ๊ฐ)
spring.application.name=first
server.port=19091
eureka.client.service-url.defaultZone=http://localhost:19090/eureka/
spring.application.name=second
server.port=19092
eureka.client.service-url.defaultZone=http://localhost:19090/eureka/

4. ๋ Eureka Client ์ ํ๋ฆฌ์ผ์ด์ ์คํ ํ ๋ค์ ์ ์

๋ก๋๋ฐธ๋ฐ์ฑ
๋ก๋ ๋ฐธ๋ฐ์ฑ์ ๋คํธ์ํฌ ํธ๋ํฝ์ ์ฌ๋ฌ ์๋ฒ๋ก ๋ถ์ฐ์์ผ ์๋ฒ์ ๋ถํ๋ฅผ ์ค์ด๊ณ , ์์คํ ์ ์ฑ๋ฅ๊ณผ ๊ฐ์ฉ์ฑ์ ๋์ด๋ ๊ธฐ์
- ์๋ฒ ๊ฐ ํธ๋ํฝ์ ๊ณ ๋ฅด๊ฒ ๋ถ๋ฐฐํ์ฌ ํน์ ์๋ฒ์ ๋ถํ๊ฐ ์ง์ค๋๋ ๊ฒ์ ๋ฐฉ์ง
- ์ข ๋ฅ: ํด๋ผ์ด์ธํธ ์ฌ์ด๋ ๋ก๋ ๋ฐธ๋ฐ์ฑ, ์๋ฒ ์ฌ์ด๋ ๋ก๋ ๋ฐธ๋ฐ์ฑ
ํด๋ผ์ด์ธํธ ์ฌ์ด๋ ๋ก๋ ๋ฐธ๋ฐ์ฑ
ํด๋ผ์ด์ธํธ ์ฌ์ด๋ ๋ก๋ ๋ฐธ๋ฐ์ฑ์ ํด๋ผ์ด์ธํธ๊ฐ ์ง์ ์ฌ๋ฌ ์๋ฒ ์ค ํ๋๋ฅผ ์ ํํด ์์ฒญ์ ๋ณด๋ด๋ ๋ฐฉ์์ผ๋ก, ํด๋ผ์ด์ธํธ๋ ์๋ฒ์ ๋ชฉ๋ก์ ๊ฐ์ง๊ณ ์์ผ๋ฉฐ ์ด๋ฅผ ๋ฐํ์ผ๋ก ๋ก๋ ๋ฐธ๋ฐ์ฑ์ ์ํํจ
FeignClient
FeignClient๋ Spring Cloud์์ ์ ๊ณตํ๋ HTTP ํด๋ผ์ด์ธํธ๋ก, ์ ์ธ์ ์ผ๋ก RESTful ์น ์๋น์ค๋ฅผ ํธ์ถํ ์ ์์
๋ํ, Eureka์ ๊ฐ์ ์๋น์ค ๋์ค์ปค๋ฒ๋ฆฌ์ ์ฐ๋ํ์ฌ ๋์ ์ผ๋ก ์๋น์ค ์ธ์คํด์ค๋ฅผ ์กฐํํ๊ณ ๋ก๋ ๋ฐธ๋ฐ์ฑ์ ์ํํจ
- ์ ์ธ์ HTTP ํด๋ผ์ด์ธํธ: ์ธํฐํ์ด์ค์ ์ ๋ ธํ ์ด์ ์ ์ฌ์ฉํด REST API ํธ์ถํ ์ ์์
- Eureka ์ฐ๋: Eureka์ ํตํฉํด ์๋น์ค ์ธ์คํด์ค ๋ชฉ๋ก์ ๋์ ์ผ๋ก ์กฐํํ๊ณ ๋ก๋ ๋ฐธ๋ฐ์ฑ ์ํ
- ์๋ ๋ก๋ ๋ฐธ๋ฐ์ฑ: Ribbon์ด ํตํฉ๋์ด ์์ด ์๋์ผ๋ก ๋ก๋ ๋ฐธ๋ฐ์ฑ ์ํ
Ribbon
Ribbon์ ๋ทํ๋ฆญ์ค๊ฐ ๊ฐ๋ฐํ ํด๋ผ์ด์ธํธ ์ฌ์ด๋ ๋ก๋ ๋ฐธ๋ฐ์๋ก, ๋ง์ดํฌ๋ก์๋น์ค ์ํคํ ์ฒ์์ ์๋น์ค ์ธ์คํด์ค ๊ฐ์ ๋ถํ๋ฅผ ๋ถ์ฐํจ
๋ค์ํ ๋ก๋ ๋ฐธ๋ฐ์ฑ ์๊ณ ๋ฆฌ์ฆ์ ์ง์ํ๋ฉฐ, Eureka์ ๊ฐ์ ์๋น์ค ๋์ค์ปค๋ฒ๋ฆฌ์ ์ฐ๋ํด ์ฌ์ฉํจ
- ์๋ฒ ๋ฆฌ์คํธ ์ ๊ณต์: Eureka ๋ฑ์ผ๋ก๋ถํฐ ์๋น์ค ์ธ์คํด์ค ๋ฆฌ์คํธ๋ฅผ ์ ๊ณต๋ฐ์ ๋ก๋ ๋ฐธ๋ฐ์ฑ์ ์ฌ์ฉํจ
- ๋ก๋ ๋ฐธ๋ฐ์ฑ ์๊ณ ๋ฆฌ์ฆ: ๋ผ์ด๋ ๋ก๋น, ๊ฐ์ค์น ๊ธฐ๋ฐ ๋ฑ ๋ค์ํ ๋ก๋ ๋ฐธ๋ฐ์ฑ ์๊ณ ๋ฆฌ์ฆ ์ง์
- Failover: ์์ฒญ ์คํจ ์ ๋ค๋ฅธ ์ธ์คํด์ค๋ก ์๋ ์ ํ
FeignClient์ Ribbon ์ค์
1. ๊ธฐ๋ณธ ์ค์
- FeignClient์ Ribbon์ ์ฌ์ฉํ๋ ค๋ฉด Spring Boot ์ ํ๋ฆฌ์ผ์ด์ ์ ์์กด์ฑ์ ์ถ๊ฐํด์ผ ํจ
dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
}
@SpringBootApplication
@EnableFeignClients
public class MyServiceApplication {
public static void main(String[] args) {
SpringApplication.run(MyServiceApplication.class, args);
}
}
2. FeignClient ์ธํฐํ์ด์ค ์์ฑ
FeignClient ์ธํฐํ์ด์ค๋ฅผ ์์ฑํด ์๋น์ค ํธ์ถ ์ํํจ(ํธ์ถํ๊ณ ์ ํ๋ ์๋น์ค ์์ฑ)
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "my-service")
public interface MyServiceClient {
@GetMapping("/endpoint")
String getResponse(@RequestParam(name = "param") String param);
}
๋ก๋ ๋ฐธ๋ฐ์ฑ ์๊ณ ๋ฆฌ์ฆ
๋ผ์ด๋ ๋ก๋น
๊ฐ ์๋ฒ์ ์์ฐจ์ ์ผ๋ก ์์ฒญ์ ๋ถ๋ฐฐํ๋ ๋ฐฉ์์ผ๋ก, ๊ฐ๋จํ๊ณ ๊ณตํํ๊ฒ ํธ๋ํฝ์ ๋ถ์ฐํจ
๊ฐ์ค์น ๊ธฐ๋ฐ ๋ก๋ ๋ฐธ๋ฐ์ฑ
๊ฐ ์๋ฒ์ ๊ฐ์ค์น๋ฅผ ๋ถ์ฌํ๊ณ , ๊ฐ์ค์น์ ๋น๋กํ์ฌ ์์ฒญ์ ๋ถ๋ฐฐํ๋ ๋ฐฉ์์ผ๋ก, ์๋ฒ์ ์ฑ๋ฅ์ด๋ ๋คํธ์ํฌ ์ํ์ ๋ฐ๋ผ ๊ฐ์ค์น๋ฅผ ์กฐ์ ํจ
๊ธฐํ ์๊ณ ๋ฆฌ์ฆ
- ์ต์ ์ฐ๊ฒฐ: ํ์ฌ ์ฐ๊ฒฐ๋ ํด๋ผ์ด์ธํธ ์๊ฐ ๊ฐ์ฅ ์ ์ ์๋ฒ๋ก ์์ฒญ์ ๋ณด๋ด๋ ๋ฐฉ์
- ์๋ต ์๊ฐ ๊ธฐ๋ฐ: ์๋ฒ์ ์๋ต ์๊ฐ์ ๊ธฐ์ค์ผ๋ก ๊ฐ์ฅ ๋น ๋ฅธ ์๋ฒ๋ก ์์ฒญ์ ๋ณด๋ด๋ ๋ฐฉ์
FeignClient์ Eureka ์ฐ๋
1. Eureka ์ค์
- Eureka์ FeignClient๋ฅผ ํจ๊ป ์ฌ์ฉํ๋ฉด ๋์ ์ผ๋ก ์๋น์ค ์ธ์คํด์ค๋ฅผ ์กฐํํ์ฌ ๋ก๋ ๋ฐธ๋ฐ์ฑ์ ์ํํจ
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
2. FeignClient์ Ribbon ๋์ ์๋ฆฌ
1) ์๋น์ค ์ด๋ฆ: @FeignClient(name = "my-service") ์ด๋ ธํ ์ด์ ์ Eureka์ ๋ฑ๋ก๋ ์๋น์ค ์ด๋ฆ์ ์ฐธ์กฐํจ
2) ์๋น์ค ์ธ์คํด์ค ์กฐํ: Eureka ์๋ฒ์์ my-service๋ผ๋ ์ด๋ฆ์ผ๋ก ๋ฑ๋ก๋ ์๋น์ค ์ธ์คํด์ค ๋ชฉ๋ก ์กฐํ
3) ๋ก๋ ๋ฐธ๋ฐ์ฑ: ์กฐํ๋ ์๋น์ค ์ธ์คํด์ค ๋ชฉ๋ก ์ค ํ๋๋ฅผ ์ ํํด ์์ฒญ์ ๋ณด๋. ๊ธฐ๋ณธ์ ์ผ๋ก Ribbon์ ์ฌ์ฉํด ๋ก๋ ๋ฐธ๋ฐ์ฑ ์ํํจ
4) ์์ฒญ ๋ถ๋ฐฐ: ์ฌ๋ฌ ์๋น์ค ์ธ์คํด์ค๊ฐ ์์ ๊ฒฝ์ฐ, Round Robin ๋๋ ๋ค๋ฅธ ์ค์ ๋ ๋ก๋ ๋ฐธ๋ฐ์ฑ ์๊ณ ๋ฆฌ์ฆ์ ์ฌ์ฉํด ์์ฒญ์ ๋ถ๋ฐฐํจ
์๋๋ฆฌ์ค
Order ์๋น์ค๋ Product ์๋น์ค๋ฅผ ํธ์ถํด ์ํ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์จ๋ค๊ณ ๊ฐ์
- Order ์๋น์ค ์ธ์คํด์ค: 1๊ฐ
- Product ์๋น์ค ์ธ์คํด์ค: 3๊ฐ
1) Order ์๋น์ค ์คํ: Order ์๋น์ค๊ฐ ์คํ๋๋ฉด Eureka ์๋ฒ์์ Product ์๋น์ค ์ธ์คํด์ค ๋ชฉ๋ก ๊ฐ์ ธ์ด
2) Product ์๋น์ค ํธ์ถ: Order ์๋น์ค์์ Product ์๋น์ค์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ํด FeignClient๋ฅผ ์ฌ์ฉํด ํธ์ถํจ
3) Ribbon์ ํตํ ๋ก๋ ๋ฐธ๋ฐ์ฑ: FeignClient๋ Ribbon์ ํตํด 3๊ฐ์ Product ์ธ์คํด์ค ์ค ํ๋๋ฅผ ์ ํํด ํธ์ถํจ(์ด ๊ณผ์ ์์ RR ์๊ณ ๋ฆฌ์ฆ ์ฌ์ฉํด ์์ฐจ์ ์ผ๋ก ๋ถ๋ฐฐํจ)
4) ์๋ต ์ฒ๋ฆฌ: ์ ํ๋ Product ์ธ์คํด์ค์์ ์๋ต์ ๋ฐ์ Order ์๋น์ค์ ๋ฐํํ๊ณ , ์ต์ข ์ ์ผ๋ก ํด๋ผ์ด์ธํธ์ ์๋ต์ ์ ๋ฌํจ
์ค์ต
์ ๋ ์นด ์๋ฒ ํ๋์ ์ฃผ๋ฌธ ์ธ์คํด์ค 1๊ฐ์ ๊ฐ์ ๊ธฐ๋ฅ์ ํฌํธ๋ง ๋ค๋ฅธ ์ํ ์ธ์คํด์ค 3๊ฐ๋ฅผ ์ฐ๊ฒฐํจ
- ์ํ ์์ฒญ(http://localhost:19090/order/1)ํ๋ฉด ์๋ตํ๋ ์ธ์คํด์ค์ ํฌํธ๋ฅผ ๋ฐ์ ๋ ธ์ถํจ(RR ๋ก๋๋ฐธ๋ฐ์ฑ์ผ๋ก ์ฒ๋ฆฌ)

1. ํ๋ก์ ํธ ์์ฑ
- Product ํ๋ก์ ํธ์ Order ํ๋ก์ ํธ ์ถ๊ฐ(์ ๋ ์นด ์๋ฒ๋ ์์์ ๋ง๋ ๊ฑฐ ๊ทธ๋๋ก ์ฌ์ฉ)



2. Product ์๋ฒ ์์ฑ
package com.spring_cloud.eureka.client.product;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients // FeignClient ํ์ฑํ
public class ProductApplication {
public static void main(String[] args) {
SpringApplication.run(ProductApplication.class, args);
}
}
package com.spring_cloud.eureka.client.product;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ProductController {
// application.properties์ ์์ฑํ server.port ๊ฐ์ ธ์ค๊ธฐ
@Value("${server.port}")
private String serverPort; // ์ ํ๋ฆฌ์ผ์ด์
ํฌํธ
@GetMapping("/product/{id}")
public String getProduct(@PathVariable("id") String id) {
return "Product " + id + " info!!! From port :" + serverPort;
}
}
// application.yml ์ค์ ์ถ๊ฐ
spring:
application:
name: product-service
server:
port: 19092
eureka:
client:
service-url:
defaultZone: http://localhost:19090/eureka/
- Product ์๋ฒ๋ฅผ 19092, 19093, 19094 ์ด 3๊ฐ์ ํฌํธ์ ์คํํด์ผํจ(์ธํ ๋ฆฌ์ ์ด ๋ฐฉ๋ฒ)




3. Order ์๋ฒ ์์ฑ
@SpringBootApplication
@EnableFeignClients // ์ถ๊ฐ
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
- Product ์๋ฒ ํธ์ถ ์ฝ๋ ์์ฑ ํ์
package com.spring_cloud.eureka.client.order;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(name = "product-service") // Eureka ์๋ฒ์์ 'product-service' ์๋ณ์๋ก ์ฐพ์์ ํธ์ถ
public interface ProductClient {
@GetMapping("/product/{id}")
String getProduct(@PathVariable("id") String id);
}
- OrderService์์ Product ํธ์ถํ ํ String ์กฐํฉ ํ ๋ค์ Controller๋ก ์๋ต ๋ฐํ
package com.spring_cloud.eureka.client.order;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class OrderService {
// @RequiredArgsConstructor ์ ๋
ธํ
์ด์
์ผ๋ก ์ฃผ์
๋ฐ๊ธฐ
private final ProductClient productClient;
public String getProductInfo(String productId) {
return productClient.getProduct(productId);
}
public String getOrder(String orderId) {
if(orderId.equals("1")){
String productId = "2";
String productInfo = getProductInfo(productId);
return "Your order is " + orderId + " and " + productInfo;
}
return "Not exist order...";
}
}
package com.spring_cloud.eureka.client.order;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
@GetMapping("/order/{orderId}")
public String getOrder(@PathVariable("orderId") String orderId) {
return orderService.getOrder(orderId);
}
}



- ์ด๋ ํฌํธ๋ฅผ ๊ฐ์ง ์๋ฒ์์ ๊ฐ์ ธ์ค๋์ง ํ์ธ ๊ฐ๋ฅํจ. ์ด๋ ๋ผ์ด๋ ๋ก๋น ๋ฐฉ์์ผ๋ก ๋ก๋๋ฐธ๋ฐ์ฑ ์ํ๋จ
์ํท ๋ธ๋ ์ด์ปค
์ํท ๋ธ๋ ์ด์ปค๋ ๋ง์ดํฌ๋ก์๋น์ค ๊ฐ์ ํธ์ถ ์คํจ๋ฅผ ๊ฐ์งํ๊ณ ์์คํ ์ ์ ์ฒด์ ์ธ ์์ ์ฑ์ ์ ์งํ๋ ํจํด
- ์ธ๋ถ ์๋น์ค ํธ์ถ ์คํจ ์ ๋น ๋ฅธ ์คํจ๋ฅผ ํตํด ์ฅ์ ๋ฅผ ๊ฒฉ๋ฆฌํ๊ณ , ์์คํ ์ ๋ค๋ฅธ ๋ถ๋ถ์ ์ํฅ์ ์ฃผ์ง ์๋๋ก ํจ
- ์ํ ๋ณํ: ํด๋ก์ฆ๋ → ์คํ → ํํ์คํ
Resilience4j
Resilience4j๋ ์ํท ๋ธ๋ ์ด์ปค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก, ์๋น์ค ๊ฐ์ ํธ์ถ ์คํจ๋ฅผ ๊ฐ์งํ๊ณ ์์คํ ์ ์์ ์ฑ์ ์ ์งํจ
๋ค์ํ ์ํท ๋ธ๋ ์ด์ปค ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ฉฐ, ์ฅ์ ๊ฒฉ๋ฆฌ ๋ฐ ๋น ๋ฅธ ์คํจ๋ฅผ ํตํด ๋ณต์๋ ฅ์ ๋์
1. ์ํท ๋ธ๋ ์ด์ปค ์ํ์ธ ํด๋ก์ฆ๋, ์คํ, ํํ-์คํ ์ํ๋ฅผ ํตํด ํธ์ถ ์คํจ๋ฅผ ๊ด๋ฆฌ
- ํด๋ก์ฆ๋(Closed): ๊ธฐ๋ณธ ์ํ๋ก, ๋ชจ๋ ์์ฒญ์ ํต๊ณผ์ํด
- ์ด ์ํ์์ ํธ์ถ์ด ์คํจํ๋ฉด ์คํจ ์นด์ดํฐ๊ฐ ์ฆ๊ฐํจ
- ์คํจ์จ์ด ์ค์ ๋ ์๊ณ๊ฐ(ex: 50%)์ ์ด๊ณผํ๋ฉด ์ํท ๋ธ๋ ์ด์ปค๊ฐ ์คํ ์ํ๋ก ์ ํ๋จ
- ex) ์ต๊ทผ 5๋ฒ์ ํธ์ถ ์ค 3๋ฒ์ด ์คํจํด ์คํจ์จ์ด 60%์ ๋๋ฌํ๋ฉด ์คํ ์ํ๋ก ์ ํ๋จ
- ์คํ(Open): ์ํท ๋ธ๋ ์ด์ปค๊ฐ ์คํ ์ํ๋ก ์ ํ๋๋ฉด ๋ชจ๋ ์์ฒญ์ ์ฆ์ ์คํจ๋ก ์ฒ๋ฆฌ
- ์ด ์ํ์์ ์์ฒญ์ด ์คํจํ์ง ์๊ณ ๋ฐ๋ก ์๋ฌ ์๋ต์ ๋ฐํํจ
- ์ค์ ๋ ๋๊ธฐ ์๊ฐ์ด ์ง๋ ํ, ์ํท ๋ธ๋ ์ด์ปค๋ ํํ-์คํ ์ํ๋ก ์ ํ๋จ
- ex) ์ํท ๋ธ๋ ์ด์ปค๊ฐ ์คํ ์ํ๋ก ์ ํ๋๊ณ 20์ด ๋์ ๋ชจ๋ ์์ฒญ์ด ์ฐจ๋จ๋จ
- ํํ-์คํ(Half-Open): ์คํ ์ํ์์ ๋๊ธฐ ์๊ฐ์ด ์ง๋๋ฉด ์ํท ๋ธ๋ ์ด์ปค๋ ํํ-์คํ ์ํ๋ก ์ ํ
- ํํ-์คํ ์ํ์์๋ ์ ํ๋ ์์ ์์ฒญ์ ํ์ฉํ์ฌ ์์คํ ์ด ์ ์ ์ํ๋ก ๋ณต๊ตฌ๋์๋์ง ํ์ธ
- ์์ฒญ์ด ์ฑ๊ณตํ๋ฉด ์ํท ๋ธ๋ ์ด์ปค๋ ํด๋ก์ฆ๋ ์ํ๋ก ์ ํ
- ์์ฒญ์ด ๋ค์ ์คํจํ๋ฉด ์ํท ๋ธ๋ ์ด์ปค๋ ๋ค์ ์คํ ์ํ๋ก ์ ํ
- ex) ํํ-์คํ ์ํ์์ 3๊ฐ์ ์์ฒญ์ ํ์ฉํ๊ณ , ๋ชจ๋ ์ฑ๊ณตํ๋ฉด ํด๋ก์ฆ๋ ์ํ๋ก ์ ํ๋จ. ๋ง์ฝ ํ๋๋ผ๋ ์คํจํ๋ฉด ๋ค์ ์คํ ์ํ๋ก ์ ํ๋จ
2. Fallback: ํธ์ถ ์คํจ ์ ๋์ฒด ๋ก์ง์ ์ ๊ณตํด ์์คํ ์์ ์ฑ ํ๋ณด
3. ๋ชจ๋ํฐ๋ง: ์ํท ๋ธ๋ ์ด์ปค ์ํ๋ฅผ ๋ชจ๋ํฐ๋งํ๊ณ ๊ด๋ฆฌํ ์ ์๋ ๋ค์ํ ๋๊ตฌ ์ ๊ณต
Resilience4j ์ค์
- Resilience4j๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด Spring Boot ์ ํ๋ฆฌ์ผ์ด์ ์ ์์กด์ฑ์ ์ถ๊ฐํด์ผ ํจ
# resilience4j ์์กด์ฑ์ spring starter์์ ์ถ๊ฐํ์ฌ ์ฌ์ฉํ์ง ์์ ๊ฒ์
๋๋ค.
# ”io.github.resilience4j:resilience4j-spring-boot3:2.2.0” ๋ฅผ ์ฌ์ฉํฉ๋๋ค.
# ”boot3” ์์ ์ฃผ์ ํ์ธ์.
dependencies {
implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0'
implementation 'org.springframework.boot:spring-boot-starter-aop'
}
- application.yml ํ์ผ์ Resilience4j ์ค์ ์ถ๊ฐ
resilience4j:
circuitbreaker:
configs:
default: # ๊ธฐ๋ณธ ๊ตฌ์ฑ ์ด๋ฆ
registerHealthIndicator: true # ์ ํ๋ฆฌ์ผ์ด์
์ ํฌ์ค ์ฒดํฌ์ ์ํท ๋ธ๋ ์ด์ปค ์ํ๋ฅผ ์ถ๊ฐํ์ฌ ๋ชจ๋ํฐ๋ง ๊ฐ๋ฅ
# ์ํท ๋ธ๋ ์ด์ปค๊ฐ ๋์ํ ๋ ์ฌ์ฉํ ์ฌ๋ผ์ด๋ฉ ์๋์ฐ์ ํ์
์ ์ค์
# COUNT_BASED: ๋ง์ง๋ง N๋ฒ์ ํธ์ถ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ํ๋ฅผ ๊ฒฐ์
# TIME_BASED: ๋ง์ง๋ง N์ด ๋์์ ํธ์ถ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ํ๋ฅผ ๊ฒฐ์
slidingWindowType: COUNT_BASED # ์ฌ๋ผ์ด๋ฉ ์๋์ฐ์ ํ์
์ ํธ์ถ ์ ๊ธฐ๋ฐ(COUNT_BASED)์ผ๋ก ์ค์
# ์ฌ๋ผ์ด๋ฉ ์๋์ฐ์ ํฌ๊ธฐ๋ฅผ ์ค์
# COUNT_BASED์ผ ๊ฒฝ์ฐ: ์ต๊ทผ N๋ฒ์ ํธ์ถ์ ์ ์ฅ
# TIME_BASED์ผ ๊ฒฝ์ฐ: ์ต๊ทผ N์ด ๋์์ ํธ์ถ์ ์ ์ฅ
slidingWindowSize: 5 # ์ฌ๋ผ์ด๋ฉ ์๋์ฐ์ ํฌ๊ธฐ๋ฅผ 5๋ฒ์ ํธ์ถ๋ก ์ค์
minimumNumberOfCalls: 5 # ์ํท ๋ธ๋ ์ด์ปค๊ฐ ๋์ํ๊ธฐ ์ํด ํ์ํ ์ต์ํ์ ํธ์ถ ์๋ฅผ 5๋ก ์ค์
slowCallRateThreshold: 100 # ๋๋ฆฐ ํธ์ถ์ ๋น์จ์ด ์ด ์๊ณ๊ฐ(100%)์ ์ด๊ณผํ๋ฉด ์ํท ๋ธ๋ ์ด์ปค๊ฐ ๋์
slowCallDurationThreshold: 60000 # ๋๋ฆฐ ํธ์ถ์ ๊ธฐ์ค ์๊ฐ(๋ฐ๋ฆฌ์ด)์ผ๋ก, 60์ด ์ด์ ๊ฑธ๋ฆฌ๋ฉด ๋๋ฆฐ ํธ์ถ๋ก ๊ฐ์ฃผ
failureRateThreshold: 50 # ์คํจ์จ์ด ์ด ์๊ณ๊ฐ(50%)์ ์ด๊ณผํ๋ฉด ์ํท ๋ธ๋ ์ด์ปค๊ฐ ๋์
permittedNumberOfCallsInHalfOpenState: 3 # ์ํท ๋ธ๋ ์ด์ปค๊ฐ Half-open ์ํ์์ ํ์ฉํ๋ ์ต๋ ํธ์ถ ์๋ฅผ 3์ผ๋ก ์ค์
# ์ํท ๋ธ๋ ์ด์ปค๊ฐ Open ์ํ์์ Half-open ์ํ๋ก ์ ํ๋๊ธฐ ์ ์ ๊ธฐ๋ค๋ฆฌ๋ ์๊ฐ
waitDurationInOpenState: 20s # Open ์ํ์์ Half-open ์ํ๋ก ์ ํ๋๊ธฐ ์ ์ ๋๊ธฐํ๋ ์๊ฐ์ 20์ด๋ก ์ค์
Fallback ๋ฉ์ปค๋์ฆ
Fallback ๋ฉ์๋๋ ์ธ๋ถ ์๋น์ค ํธ์ถ์ด ์คํจํ์ ๋ ๋์ฒด ๋ก์ง์ ์ ๊ณตํ๋ ๋ฉ์๋
@Service
public class MyService {
@CircuitBreaker(name = "myService", fallbackMethod = "fallbackMethod")
public String myMethod() {
// ์ธ๋ถ ์๋น์ค ํธ์ถ
return externalService.call();
}
public String fallbackMethod(Throwable t) {
return "Fallback response";
}
}
์ฅ์
- ์์คํ ์ ์์ ์ฑ์ ๋์ด๊ณ , ์ฅ์ ๊ฐ ๋ฐ์ํด๋ ์ฌ์ฉ์์๊ฒ ์ผ์ ํ ์๋ต ์ ๊ณต ๊ฐ๋ฅ
- ์ฅ์ ๊ฐ ๋ค๋ฅธ ์๋น์ค์ ์ ํ๋๋ ๊ฒ์ ๋ฐฉ์งํจ
Resilience4j ๋์๋ณด๋
Resilience4j ๋์๋ณด๋๋ฅผ ์ฌ์ฉํด ์ํท ๋ธ๋ ์ด์ปค์ ์ํ๋ฅผ ๋ชจ๋ํฐ๋งํ ์ ์์
dependencies {
implementation 'io.github.resilience4j:resilience4j-micrometer'
implementation 'io.micrometer:micrometer-registry-prometheus'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
}
- application.yml ํ์ผ์ ์ค์ ์ถ๊ฐ
management:
endpoints:
web:
exposure:
include: prometheus
prometheus:
metrics:
export:
enabled: true
- 'http://${hostname}:${port}/actuator/prometheus'์ ์ ์ํด ์ํท๋ธ๋ ์ด์ปค ํญ๋ชฉ ํ์ธ ๊ฐ๋ฅํจ
- Prometheus์ Grafana๋ฅผ ์ฌ์ฉํด Resilience4j ์ํท ๋ธ๋ ์ด์ปค์ ์ํ๋ฅผ ์ค์๊ฐ์ผ๋ก ๋ชจ๋ํฐ๋งํ ์ ์์
- Prometheus๋ฅผ ํตํด ์์ง๋ ๋ฉํธ๋ฆญ์ Grafana ๋์๋ณด๋์์ ์๊ฐํ ๊ฐ๋ฅ
Resilience4j์ Spring Cloud ์ฐ๋
- Resilience4j๋ Spring Cloud Netflix ํจํค์ง์ ์ผ๋ถ๋ก, Eureka์ Ribbon ๋ฑ ๋ค๋ฅธ Spring Cloud ๊ตฌ์ฑ ์์์ ์ฝ๊ฒ ํตํฉ ๊ฐ๋ฅ
- Spring Cloud์ ์๋น์ค ๋์ค์ปค๋ฒ๋ฆฌ์ ๋ก๋ ๋ฐธ๋ฐ์ฑ์ ํ์ฉํด ๋์ฑ ์์ ์ ์ธ ๋ง์ดํฌ๋ก์๋น์ค ์ํคํ ์ฒ ๊ตฌ์ถ ๊ฐ๋ฅ
์ค์ต
๏นกํด๋น ์ค์ต์์๋ Eureka ์ฌ์ฉ X
ํ๋ก์ ํธ์์ ์ํ์ ์กฐํํ๋ ๊ฒ์ ๊ฐ์ ํ๊ณ ,
- ์ํ ์์ด๋ 111์ ํธ์ถํ๋ฉด ์๋ฌ ๋ฐ์์์ผ fallbackMethod๋ฅผ ์คํ ํ์ธ
- ์ด๋ฒคํธ๋ฆฌ์ค๋๋ฅผ ์ฌ์ฉํด ์ํท๋ธ๋ ์ด์ปค์ ์ํ ์กฐํ ํ์ธ
1. ํ๋ก์ ํธ ์์ฑ ๋ฐ ์์กด์ฑ ์ถ๊ฐ

plugins {
id 'java'
id 'org.springframework.boot' version '3.1.0'
id 'io.spring.dependency-management' version '1.1.7'
}
group = 'com.spring-cloud.resilience4j'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0'
implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'io.micrometer:micrometer-registry-prometheus'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
tasks.named('test'){
useJUnitPlatform()
}
2. Product ๊ด๋ จ ํ์ผ ์์ฑ(Product, ProductController, ProductService)
package com.spring_cloud.resilience4j.sample.products;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product {
private String id;
private String title;
}
package com.spring_cloud.resilience4j.sample.products;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
public class ProductController {
private final ProductService productService;
@GetMapping("/product/{id}")
public Product getProduct(@PathVariable String id) {
return productService.getProductDetails(id);
}
}
package com.spring_cloud.resilience4j.sample.products;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class ProductService {
public Product getProductDetails(String productId) {
return new Product(productId, "Sample Product");
}
}
3. ์ํท ๋ธ๋ ์ด์ปค ์ถ๊ฐ
- ProductService ๋น์ฆ๋์ค ๋ก์ง์ '์ํ ์์ด๋ 111์ ํธ์ถ' ์ ์๋ฌ ๋ฐ์์ ์ํ ์ํท ๋ธ๋ ์ด์ปค ์ถ๊ฐ
package com.spring_cloud.resilience4j.sample.products;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class ProductService {
@CircuitBreaker(name = "productService", fallbackMethod = "fallbackGetProductDetails")
public Product getProductDetails(String productId) {
if("111".equals(productId)){
throw new RuntimeException("Empty response body");
}
return new Product(productId, "Sample Product");
}
public Product fallbackGetProductDetails(String productId, Throwable t) {
return new Product(productId, "Fallback Product");
}
}
4. application.yml ์ค์
spring:
application:
name: sample
server:
port: 19090
resilience4j:
circuitbreaker:
configs:
default: # ๊ธฐ๋ณธ ๊ตฌ์ฑ ์ด๋ฆ
registerHealthIndicator: true # ์ ํ๋ฆฌ์ผ์ด์
์ ํฌ์ค ์ฒดํฌ์ ์ํท ๋ธ๋ ์ด์ปค ์ํ๋ฅผ ์ถ๊ฐํ์ฌ ๋ชจ๋ํฐ๋ง ๊ฐ๋ฅ
# ์ํท ๋ธ๋ ์ด์ปค๊ฐ ๋์ํ ๋ ์ฌ์ฉํ ์ฌ๋ผ์ด๋ฉ ์๋์ฐ์ ํ์
์ ์ค์
# COUNT_BASED: ๋ง์ง๋ง N๋ฒ์ ํธ์ถ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ํ๋ฅผ ๊ฒฐ์
# TIME_BASED: ๋ง์ง๋ง N์ด ๋์์ ํธ์ถ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ํ๋ฅผ ๊ฒฐ์
slidingWindowType: COUNT_BASED # ์ฌ๋ผ์ด๋ฉ ์๋์ฐ์ ํ์
์ ํธ์ถ ์ ๊ธฐ๋ฐ(COUNT_BASED)์ผ๋ก ์ค์
# ์ฌ๋ผ์ด๋ฉ ์๋์ฐ์ ํฌ๊ธฐ๋ฅผ ์ค์
# COUNT_BASED์ผ ๊ฒฝ์ฐ: ์ต๊ทผ N๋ฒ์ ํธ์ถ์ ์ ์ฅ
# TIME_BASED์ผ ๊ฒฝ์ฐ: ์ต๊ทผ N์ด ๋์์ ํธ์ถ์ ์ ์ฅ
slidingWindowSize: 5 # ์ฌ๋ผ์ด๋ฉ ์๋์ฐ์ ํฌ๊ธฐ๋ฅผ 5๋ฒ์ ํธ์ถ๋ก ์ค์
minimumNumberOfCalls: 5 # ์ํท ๋ธ๋ ์ด์ปค๊ฐ ๋์ํ๊ธฐ ์ํด ํ์ํ ์ต์ํ์ ํธ์ถ ์๋ฅผ 5๋ก ์ค์
slowCallRateThreshold: 100 # ๋๋ฆฐ ํธ์ถ์ ๋น์จ์ด ์ด ์๊ณ๊ฐ(100%)์ ์ด๊ณผํ๋ฉด ์ํท ๋ธ๋ ์ด์ปค๊ฐ ๋์
slowCallDurationThreshold: 60000 # ๋๋ฆฐ ํธ์ถ์ ๊ธฐ์ค ์๊ฐ(๋ฐ๋ฆฌ์ด)์ผ๋ก, 60์ด ์ด์ ๊ฑธ๋ฆฌ๋ฉด ๋๋ฆฐ ํธ์ถ๋ก ๊ฐ์ฃผ
failureRateThreshold: 50 # ์คํจ์จ์ด ์ด ์๊ณ๊ฐ(50%)์ ์ด๊ณผํ๋ฉด ์ํท ๋ธ๋ ์ด์ปค๊ฐ ๋์
permittedNumberOfCallsInHalfOpenState: 3 # ์ํท ๋ธ๋ ์ด์ปค๊ฐ Half-open ์ํ์์ ํ์ฉํ๋ ์ต๋ ํธ์ถ ์๋ฅผ 3์ผ๋ก ์ค์
# ์ํท ๋ธ๋ ์ด์ปค๊ฐ Open ์ํ์์ Half-open ์ํ๋ก ์ ํ๋๊ธฐ ์ ์ ๊ธฐ๋ค๋ฆฌ๋ ์๊ฐ
waitDurationInOpenState: 20s # Open ์ํ์์ Half-open ์ํ๋ก ์ ํ๋๊ธฐ ์ ์ ๋๊ธฐํ๋ ์๊ฐ์ 20์ด๋ก ์ค์
management:
endpoints:
web:
exposure:
include: prometheus
prometheus:
metrics:
export:
enabled: true


5. ์ด๋ฒคํธ ๋ฆฌ์ค๋ ์ถ๊ฐํด ์ํท ๋ธ๋ ์ด์ปค ์ํ ํ์ธ
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class ProductService {
private final Logger log = LoggerFactory.getLogger(getClass());
private final CircuitBreakerRegistry circuitBreakerRegistry;
@PostConstruct
public void registerEventListener() {
circuitBreakerRegistry.circuitBreaker("productService").getEventPublisher()
.onStateTransition(event -> log.info("#######CircuitBreaker State Transition: {}", event)) // ์ํ ์ ํ ์ด๋ฒคํธ ๋ฆฌ์ค๋
.onFailureRateExceeded(event -> log.info("#######CircuitBreaker Failure Rate Exceeded: {}", event)) // ์คํจ์จ ์ด๊ณผ ์ด๋ฒคํธ ๋ฆฌ์ค๋
.onCallNotPermitted(event -> log.info("#######CircuitBreaker Call Not Permitted: {}", event)) // ํธ์ถ ์ฐจ๋จ ์ด๋ฒคํธ ๋ฆฌ์ค๋
.onError(event -> log.info("#######CircuitBreaker Error: {}", event)); // ์ค๋ฅ ๋ฐ์ ์ด๋ฒคํธ ๋ฆฌ์ค๋
}
@CircuitBreaker(name = "productService", fallbackMethod = "fallbackGetProductDetails")
public Product getProductDetails(String productId) {
log.info("###Fetching product details for productId: {}", productId);
if ("111".equals(productId)) {
log.warn("###Received empty body for productId: {}", productId);
throw new RuntimeException("Empty response body");
}
return new Product(
productId,
"Sample Product"
);
}
public Product fallbackGetProductDetails(String productId, Throwable t) {
log.error("####Fallback triggered for productId: {} due to: {}", productId, t.getMessage());
return new Product(
productId,
"Fallback Product"
);
}
// ์ด๋ฒคํธ ์ค๋ช
ํ
// +---------------------------+-------------------------------------------------+--------------------------------------------+
// | ์ด๋ฒคํธ | ์ค๋ช
| ๋ก๊ทธ ์ถ๋ ฅ |
// +---------------------------+-------------------------------------------------+--------------------------------------------+
// | ์ํ ์ ํ (Closed -> Open) | ์ฐ์๋ ์คํจ๋ก ์ธํด ์ํท ๋ธ๋ ์ด์ปค๊ฐ ์คํ ์ํ๋ก ์ ํ๋๋ฉด ๋ฐ์ | CircuitBreaker State Transition: ... |
// | ์คํจ์จ ์ด๊ณผ | ์ค์ ๋ ์คํจ์จ ์๊ณ์น๋ฅผ ์ด๊ณผํ๋ฉด ๋ฐ์ | CircuitBreaker Failure Rate Exceeded: ... |
// | ํธ์ถ ์ฐจ๋จ | ์ํท ๋ธ๋ ์ด์ปค๊ฐ ์คํ ์ํ์ผ ๋ ํธ์ถ์ด ์ฐจ๋จ๋๋ฉด ๋ฐ์ | CircuitBreaker Call Not Permitted: ... |
// | ์ค๋ฅ ๋ฐ์ | ์ํท ๋ธ๋ ์ด์ปค ๋ด๋ถ์์ ํธ์ถ์ด ์คํจํ๋ฉด ๋ฐ์ | CircuitBreaker Error: ... |
// +---------------------------+-------------------------------------------------+--------------------------------------------+
// +------------------------------------------+-------------------------------------------+-----------------------------------------------------------------+
// | ์ด๋ฒคํธ | ์ค๋ช
| ๋ก๊ทธ ์ถ๋ ฅ |
// +------------------------------------------+-------------------------------------------+-----------------------------------------------------------------+
// | ๋ฉ์๋ ํธ์ถ | ์ ํ ์ ๋ณด๋ฅผ ์ป๊ธฐ ์ํด ๋ฉ์๋๋ฅผ ํธ์ถ | ###Fetching product details for productId: ... |
// | (์ฑ๊ณต ์) ์ํท ๋ธ๋ ์ด์ปค ๋ด๋ถ์์ ํธ์ถ ์ฑ๊ณต | ๋ฉ์๋ ํธ์ถ์ด ์ฑ๊ณตํ์ฌ ์ ์์ ์ธ ์๋ต์ ๋ฐํ | |
// | (์คํจ ์) ์ํท ๋ธ๋ ์ด์ปค ๋ด๋ถ์์ ํธ์ถ ์คํจ | ๋ฉ์๋ ํธ์ถ์ด ์คํจํ์ฌ ์์ธ๊ฐ ๋ฐ์ | #######CircuitBreaker Error: ... |
// | (์คํจ ์) ์คํจ ํ์ ์ฆ๊ฐ | ์ํท ๋ธ๋ ์ด์ปค๊ฐ ์คํจ ํ์๋ฅผ ์ฆ๊ฐ์ํด | |
// | (์คํจ์จ ์ด๊ณผ ์) ์คํจ์จ ์ด๊ณผ | ์ค์ ๋ ์คํจ์จ ์๊ณ์น๋ฅผ ์ด๊ณผํ๋ฉด ๋ฐ์ | #######CircuitBreaker Failure Rate Exceeded: ... |
// | (์คํจ์จ ์ด๊ณผ ์) ์ํ ์ ํ (Closed -> Open) | ์ฐ์๋ ์คํจ๋ก ์ธํด ์ํท ๋ธ๋ ์ด์ปค๊ฐ ์คํ ์ํ๋ก ์ ํ๋จ | #######CircuitBreaker State Transition: Closed -> Open at ... |
// | (์คํ ์ํ ์) ํธ์ถ ์ฐจ๋จ | ์ํท ๋ธ๋ ์ด์ปค๊ฐ ์คํ ์ํ์ผ ๋ ํธ์ถ์ด ์ฐจ๋จ๋จ | #######CircuitBreaker Call Not Permitted: ... |
// | (์คํ ์ํ ์) ํด๋ฐฑ ๋ฉ์๋ ํธ์ถ | ๋ฉ์๋ ํธ์ถ์ด ์ฐจ๋จ๋ ๊ฒฝ์ฐ ํด๋ฐฑ ๋ฉ์๋ ํธ์ถ | ####Fallback triggered for productId: ... due to: ... |
// +------------------------------------------+-------------------------------------------+-----------------------------------------------------------------+
}

6. ํ๋ก๋ฉํ ์ฐ์ค๋ก ์ํท ๋ธ๋ ์ด์ปค ์ ๋ณด ํ์ธ ๊ฐ๋ฅ
- http://localhost:19090/actuator/prometheus

API ๊ฒ์ดํธ์จ์ด(Spring Cloud Gateway)
API ๊ฒ์ดํธ์จ์ด๋ ํด๋ผ์ด์ธํธ์ ์์ฒญ์ ๋ฐ์ ๋ฐฑ์๋ ์๋น์ค๋ก ๋ผ์ฐํ ํ๊ณ , ๋ค์ํ ๋ถ๊ฐ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ ์ค๊ฐ ์๋ฒ๋ก,
ํด๋ผ์ด์ธํธ์ ์๋น์ค ๊ฐ์ ๋จ์ผ ์ง์ ์ ์ญํ ์ ํ๋ฉฐ, ๋ณด์, ๋ก๊น , ๋ชจ๋ํฐ๋ง, ์์ฒญ ํํฐ๋ง ๋ฑ์ ์ฒ๋ฆฌํจ
- ๋ผ์ฐํ : ํด๋ผ์ด์ธํธ ์์ฒญ์ ์ ์ ํ ์๋น์ค๋ก ์ ๋ฌ
- ์ธ์ฆ ๋ฐ ๊ถํ ๋ถ์ฌ: ์์ฒญ์ ์ธ์ฆ ๋ฐ ๊ถํ์ ๊ฒ์ฆ
- ๋ก๋ ๋ฐธ๋ฐ์ฑ: ์ฌ๋ฌ ์๋น์ค ์ธ์คํด์ค ๊ฐ์ ๋ถํ ๋ถ์ฐ
- ๋ชจ๋ํฐ๋ง ๋ฐ ๋ก๊น : ์์ฒญ ๋ฐ ์๋ต์ ๋ก๊น ํ๊ณ ๋ชจ๋ํฐ๋ง
- ์์ฒญ ๋ฐ ์๋ต ๋ณํ: ์์ฒญ๊ณผ ์๋ต์ ๋ณํํ๊ฑฐ๋ ํํฐ๋ง
Spring Cloud Gateway
Spring ํ๋ก์ ํธ์ ์ผํ์ผ๋ก ๊ฐ๋ฐ๋ API ๊ฒ์ดํธ์จ์ด๋ก, ํด๋ผ์ด์ธํธ ์์ฒญ์ ์ ์ ํ ์๋น์ค๋ก ๋ผ์ฐํ ํ๊ณ ๋ค์ํ ํํฐ๋ง ๊ธฐ๋ฅ์ ์ ๊ณตํจ
Spring Cloud Netflix ํจํค์ง์ ์ผ๋ถ๋ก, ๋ง์ดํฌ๋ก์๋น์ค ์ํคํ ์ฒ์์ ๋๋ฆฌ ์ฌ์ฉ๋จ
- ๋์ ๋ผ์ฐํ : ์์ฒญ์ URL ํจํด์ ๋ฐ๋ผ ๋์ ์ผ๋ก ๋ผ์ฐํ
- ํํฐ๋ง: ์์ฒญ ์ ํ์ ๋ค์ํ ์์ ์ ์ํํ ์ ์๋ ํํฐ ์ฒด์ธ ์ ๊ณต
- ๋ชจ๋ํฐ๋ง: ์์ฒญ ๋ก๊ทธ ๋ฐ ๋ฉํธ๋ฆญ์ ํตํด ์๋น์ค ์ํ ๋ชจ๋ํฐ๋ง
- ๋ณด์: ์์ฒญ์ ์ธ์ฆ ๋ฐ ๊ถํ ๊ฒ์ฆ
Spring Cloud Gateway ์ค์
- Spring Cloud Gateway ์ฌ์ฉํ๋ ค๋ฉด ์์กด์ฑ ์ถ๊ฐํด์ผ ํจ
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
}
- application.yml ํ์ผ์ ๋ผ์ฐํ ์ค์ ์ ์
spring:
cloud:
gateway:
discovery:
locator:
enabled: true # ์๋น์ค ๋์ค์ปค๋ฒ๋ฆฌ๋ฅผ ํตํด ๋์ ์ผ๋ก ๋ผ์ฐํธ๋ฅผ ์์ฑํ๋๋ก ์ค์
routes:
- id: users-service # ๋ผ์ฐํธ ์๋ณ์
uri: lb://users-service # 'users-service'๋ผ๋ ์ด๋ฆ์ผ๋ก ๋ก๋ ๋ฐธ๋ฐ์ฑ๋ ์๋น์ค๋ก ๋ผ์ฐํ
, lb๋ ๋ก๋๋ฐธ๋ฐ์ฑ ๋ปํจ
predicates:
- Path=/users/** # /users/** ๊ฒฝ๋ก๋ก ๋ค์ด์ค๋ ์์ฒญ์ ์ด ๋ผ์ฐํธ๋ก ์ฒ๋ฆฌ
- id: orders-service # ๋ผ์ฐํธ ์๋ณ์
uri: lb://orders-service # 'orders-service'๋ผ๋ ์ด๋ฆ์ผ๋ก ๋ก๋ ๋ฐธ๋ฐ์ฑ๋ ์๋น์ค๋ก ๋ผ์ฐํ
predicates:
- Path=/orders/** #/orders/** ๊ฒฝ๋ก๋ก ๋ค์ด์ค๋ ์์ฒญ์ ์ด ๋ผ์ฐํธ๋ก ์ฒ๋ฆฌ
# Gateway๋ฅผ eureka client๋ก ๋ฑ๋กํด์ eureka server๋ฅผ ํตํด ์๋น์ค ์์น ํ์ธ
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
Spring Cloud Gateway ํํฐ๋ง
ํํฐ ์ข ๋ฅ
- Global Filter: ๋ชจ๋ ์์ฒญ์ ๋ํด ์๋ํ๋ ํํฐ
- Gateway Filter: ํน์ ๋ผ์ฐํธ์๋ง ์ ์ฉ๋๋ ํํฐ
ํํฐ ๊ตฌํ
ํํฐ๋ฅผ ๊ตฌํํ๋ ค๋ฉด GlobalFilter ๋๋ GatewayFilter ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๊ณ , filter ๋ฉ์๋๋ฅผ ์ค๋ฒ๋ผ์ด๋ํด์ผ ํจ
ํํฐ ์ฃผ์ ๊ฐ์ฒด
- Mono: ๋ฆฌ์กํฐ๋ธ ํ๋ก๊ทธ๋๋ฐ์์ 0 ๋๋ 1๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๋น๋๊ธฐ์ ์ผ๋ก ์ฒ๋ฆฌ
- Mono<Void>๋ ์๋ฌด ๋ฐ์ดํฐ๋ ๋ฐํํ์ง ์์์ ์๋ฏธ - ServerWebExchange: HTTP ์์ฒญ๊ณผ ์๋ต์ ์บก์ํํ ๊ฐ์ฒด
- exchange.getRequest()๋ก HTTP ์์ฒญ ๊ฐ์ ธ์ด
- exchange.getResponse()๋ก HTTP ์๋ต ๊ฐ์ ธ์ด - GatewayFilterChain: ์ฌ๋ฌ ํํฐ๋ฅผ ์ฒด์ธ์ฒ๋ผ ์ฐ๊ฒฐ
- chain.filter(exchange)๋ ๋ค์ ํํฐ๋ก ์์ฒญ ์ ๋ฌ
ํํฐ ์์ ๋ณ ์ข ๋ฅ
- Pre ํํฐ: ์์ฒญ์ด ์ฒ๋ฆฌ๋๊ธฐ ์ ์ ์คํ๋จ. Pre ํํฐ์์๋ ์์ฒญ์ ๊ฐ๋ก์ฑ๊ณ ํ์ํ ์์
์ ์ํํ ๋ค์, ์ฒด์ธ์ ๋ค์ ํํฐ๋ก ์์ฒญ์ ์ ๋ฌํจ
์ฆ, '๋ฉ์ธ ์์ฒญ์ธ ๋ผ์ฐํ ์์ ๋ฐ์ดํฐ ์์ฒญํ๊ณ ๊ฐ์ ธ์ค๋ ๋์' ์ ์ ์ํ
์ด๋, ์ถ๊ฐ์ ์ธ ๋น๋๊ธฐ ์์ ์ ์ํํ ํ์๊ฐ ์๊ธฐ ๋๋ฌธ์ then ๋ฉ์๋๋ฅผ ์ฌ์ฉํ ํ์๊ฐ ์์
@Component
public class PreFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// ์์ฒญ ๋ก๊น
System.out.println("Request: " + exchange.getRequest().getPath());
return chain.filter(exchange);
}
@Override
public int getOrder() { // ํํฐ์ ์์๋ฅผ ์ง์ ํฉ๋๋ค.
return -1; // ํํฐ ์์๋ฅผ ๊ฐ์ฅ ๋์ ์ฐ์ ์์๋ก ์ค์ ํฉ๋๋ค.
}
}
- Post ํํฐ: ์์ฒญ์ด ์ฒ๋ฆฌ๋ ํ, ์๋ต์ด ๋ฐํ๋๊ธฐ ์ ์ ์คํ๋จ. Post ํํฐ์์๋ ์ฒด์ธ์ ๋ค์ ํํฐ๊ฐ ์๋ฃ๋ ํ์ ์คํ๋์ด์ผ ํ๋ ์ถ๊ฐ์ ์ธ ์์ ์ ์ํํด์ผ ํจ. ๋ฐ๋ผ์ chain.filter(exchange)๋ฅผ ํธ์ถํด ๋ค์ ํํฐ๋ฅผ ์คํํ ํ, then ๋ฉ์๋๋ฅผ ์ฌ์ฉํด ์๋ต์ด ์๋ฃ๋ ํ์ ์คํํ ์์ ์ ์ ์ํจ
@Component
public class PostFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
// ์๋ต ๋ก๊น
System.out.println("Response Status: " + exchange.getResponse().getStatusCode());
}));
}
@Override
public int getOrder() {
return -1;
}
}
Spring Cloud์์ ํตํฉ
- Spring Cloud Gateway๋ Spring Cloud Netflix ํจํค์ง์ ์ผ๋ถ๋ก, Eureka์ ์ฝ๊ฒ ํตํฉ ๊ฐ๋ฅ
- Eureka๋ฅผ ํตํด ๋์ ์ผ๋ก ์๋น์ค ์ธ์คํด์ค๋ฅผ ์กฐํํด ๋ก๋ ๋ฐธ๋ฐ์ฑ๊ณผ ๋ผ์ฐํ ์ ์ํํ ์ ์์
์ค์ต
ํด๋ผ์ฐ๋ ๊ฒ์ดํธ์จ์ด + ์ ๋ ์นด + Order ์ธ์คํด์ค(1๊ฐ) + Product ์ธ์คํด์ค(2๊ฐ)๋ก ์งํ

1. ๊ฐ ์ ํ๋ฆฌ์ผ์ด์ ํฌํธ ๋ฒํธ ์์

2. Gateway ํ๋ก์ ํธ ์์ฑ ๋ฐ application.yml ์์


server:
port: 19091 # ๊ฒ์ดํธ์จ์ด ์๋น์ค๊ฐ ์คํ๋ ํฌํธ ๋ฒํธ
spring:
main:
web-application-type: reactive # Spring ์ ํ๋ฆฌ์ผ์ด์
์ด ๋ฆฌ์กํฐ๋ธ ์น ์ ํ๋ฆฌ์ผ์ด์
์ผ๋ก ์ค์ ๋จ
application:
name: gateway-service # ์ ํ๋ฆฌ์ผ์ด์
์ด๋ฆ์ 'gateway-service'๋ก ์ค์
cloud:
gateway:
routes: # Spring Cloud Gateway์ ๋ผ์ฐํ
์ค์
- id: order-service # ๋ผ์ฐํธ ์๋ณ์
uri: lb://order-service # 'order-service'๋ผ๋ ์ด๋ฆ์ผ๋ก ๋ก๋ ๋ฐธ๋ฐ์ฑ๋ ์๋น์ค๋ก ๋ผ์ฐํ
predicates:
- Path=/order/** # /order/** ๊ฒฝ๋ก๋ก ๋ค์ด์ค๋ ์์ฒญ์ ์ด ๋ผ์ฐํธ๋ก ์ฒ๋ฆฌ
- id: product-service # ๋ผ์ฐํธ ์๋ณ์
uri: lb://product-service # 'product-service'๋ผ๋ ์ด๋ฆ์ผ๋ก ๋ก๋ ๋ฐธ๋ฐ์ฑ๋ ์๋น์ค๋ก ๋ผ์ฐํ
predicates:
- Path=/product/** # /product/** ๊ฒฝ๋ก๋ก ๋ค์ด์ค๋ ์์ฒญ์ ์ด ๋ผ์ฐํธ๋ก ์ฒ๋ฆฌ
discovery:
locator:
enabled: true # ์๋น์ค ๋์ค์ปค๋ฒ๋ฆฌ๋ฅผ ํตํด ๋์ ์ผ๋ก ๋ผ์ฐํธ๋ฅผ ์์ฑํ๋๋ก ์ค์
eureka:
client:
service-url:
defaultZone: http://localhost:19090/eureka/ # Eureka ์๋ฒ์ URL์ ์ง์
3. Filter ์์ฑ
package com.spring_cloud.eureka.client.gateway;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.logging.Logger;
@Component
public class CustomPreFilter implements GlobalFilter, Ordered {
private static final Logger logger = Logger.getLogger(CustomPreFilter.class.getName());
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
logger.info("Pre Filter: Request URI: "+request.getURI());
return chain.filter(exchange); // ๋ค์ ํํฐ๋ก ์ ๋ฌ
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE; // ์ต์์ ์ฐ์ ์์
}
}
package com.spring_cloud.eureka.client.gateway;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.logging.Logger;
public class CustomPostFilter implements GlobalFilter, Ordered {
private static final Logger logger = Logger.getLogger(CustomPreFilter.class.getName());
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getResponse();
logger.info("Post Filter : Response status code is " + response.getStatusCode());
}));
}
@Override
public int getOrder() {
return 0;
}
}





๋ณด์ ๊ตฌ์ฑ(OAuth2 + JWT)
- ๋ง์ดํฌ๋ก์๋น์ค ์ํคํ ์ฒ์์๋ ๊ฐ ์๋น์ค๊ฐ ๋ ๋ฆฝ์ ์ผ๋ก ๋ฐฐํฌ๋๊ณ ํต์ ํ๊ธฐ ๋๋ฌธ์ ๋ณด์์ด ๋งค์ฐ ์ค์ํจ
- ๋ฐ์ดํฐ ๋ณดํธ, ์ธ์ฆ ๋ฐ ๊ถํ ๋ถ์ฌ, ํต์ ์ํธํ ๋ฑ์ ํตํด ์์คํ ์ ๋ณด์์ฑ์ ํ๋ณดํด์ผ ํจ
OAuth2
OAuth2๋ ํ ํฐ ๊ธฐ๋ฐ์ ์ธ์ฆ ๋ฐ ๊ถํ ๋ถ์ฌ ํ๋กํ ์ฝ๋ก, ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์ ์ด ๋ฆฌ์์ค ์์ ์์ ๊ถํ์ ์ป์ด ๋ณดํธ๋ ๋ฆฌ์์ค์ ์ ๊ทผํ ์ ์๋๋กํจ
- OAuth2 ๋ค ๊ฐ์ง ์ญํ ์ ์: ๋ฆฌ์์ค ์์ ์, ํด๋ผ์ด์ธํธ, ๋ฆฌ์์ค ์๋ฒ, ์ธ์ฆ ์๋ฒ
- Authorization Code Grant: ์ธ์ฆ ์ฝ๋๋ฅผ ์ฌ์ฉํด ์ก์ธ์ค ํ ํฐ์ ์ป๋ ๋ฐฉ์
- Implicit Grant: ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ง์ ์ก์ธ์ค ํ ํฐ์ ์ป๋ ๋ฐฉ์
- Resource Owner Passwrod Credentials Grant: ์ฌ์ฉ์ ์ด๋ฆ๊ณผ ๋น๋ฐ๋ฒํธ๋ฅผ ์ฌ์ฉํด ์ก์ธ์ค ํ ํฐ์ ์ป๋ ๋ฐฉ์
- Client Credentials Grant: ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์์ ์ ์๊ฒฉ ์ฆ๋ช ์ ์ฌ์ฉํด ์ก์ธ์ค ํ ํฐ์ ์ป๋ ๋ฐฉ์
JWT(JSON Web Token)
JWT๋ JSON ํ์์ ์๊ฐ ํฌํจ๋ ํ ํฐ์ผ๋ก, ํด๋ ์(claim)์ ํฌํจํด ์ฌ์ฉ์์ ๋ํ ์ ๋ณด ์ ๋ฌํจ
- JWT = ํค๋ + ํ์ด๋ก๋ + ์๋ช
- JWT๋ ์ํธํ๋ฅผ ํตํด ๋ฐ์ดํฐ์ ๋ฌด๊ฒฐ์ฑ๊ณผ ์ถ์ฒ ๋ณด์ฅ
- ์๊ฐ ํฌํจ: ํ ํฐ ์์ฒด์ ๋ชจ๋ ์ ๋ณด๋ฅผ ํฌํจํ๊ณ ์์ด ๋ณ๋์ ์ํ ์ ์ฅ์ด ํ์ ์์
- ๊ฐ๊ฒฐ์ฑ: ์งง๊ณ ๊ฐ๊ฒฐํ ๋ฌธ์์ด๋ก, URL, ํค๋ ๋ฑ์ ์ฝ๊ฒ ํฌํจ๋ ์ ์์
- ์๋ช
๋ฐ ์ํธํ: ๋ฐ์ดํฐ์ ๋ฌด๊ฒฐ์ฑ๊ณผ ์ธ์ฆ์ ๋ณด์ฅํจ
- JWT๋ ๋ฐ์ดํฐ๊ฐ ์ค๊ฐ์ ๋ณ๊ฒฝ๋์๋์ง ์๋์๋์ง๋ฅผ ๋ฉ์ธ์ผ๋ก ํ์ธ!
- ํ ํฐ์ ๋ด๋ถ์ ์๋ ๋ฐ์ดํฐ๋ณด๋ค๋ ํ ํฐ ๋ณ์กฐ ์ฌ๋ถ๊ฐ ์ค์ !
- JWT ํ ํฐ์ด ๋ณ์กฐ๊ฐ ์ด๋ฃจ์ด์ง๋ฉด ํต๊ณผ๊ฐ ์๋จ
์ค์ต
๊ฒ์ดํธ์จ์ด์ Pre ํํฐ์์ JWT ์ธ์ฆ์ ์งํ
- ๊ฒ์ดํธ์จ์ด: 19091
- product ์๋น์ค: 19093(์ด ์๋น์ค๋ง ํ์ฉ)
- auth ์๋น์ค: 19095
1) 'http://localhost:19091/auth/signin?user_id=admin_1'๋ก ์์ฒญ์ด ๊ฒ์ดํธ์จ์ด๋ก ์์ ๋, user_id๋ก ํ ํฐ ์์ฑ ํ ๋ฐํ
2) ํด๋ผ์ด์ธํธ๋ ํ ํฐ์ ํ์ฉํด Product๋ก ํธ์ถ
3) ๊ฒ์ดํธ์จ์ด๋ก ๋จผ์ ๋ค์ด์ ํ ํฐ์ผ๋ก ๋ก๊ทธ์ธ ์ฌ๋ถ ํ์ธ
4) ํต๊ณผํ๋ฉด Product ์๋น์ค๋ก ์ด๋
๋ง์ฝ ๊ถํ์ ํ์ธํด์ผ ํ๋ค๋ฉด, ๊ฒ์ดํธ์จ์ด → auth ํธ์ถ ํ์
- ๊ธฐ๋ณธ์ ์ธ User: Gateway → Product
- ๊ถํ์ ๊ฐ์ง User: Gateway → auth ์์ฒญ ํ ์๋ต → Gateway → Product

1. auth ์๋น์ค ์์ฑ ํ build.gradle, application.yml ์์

implementation 'io.jsonwebtoken:jjwt:0.12.6' // dependencies์ ์ถ๊ฐ
spring:
application:
name: auth-service
eureka:
client:
service-url:
defaultZone: http://localhost:19090/eureka/ # Eureka ์๋ฒ์ URL์ ์ง์
service:
jwt:
access-expiration: 3600000
secret-key: {secret-key}
server:
port: 19095
2. JWT Config ํ์ผ ์์ฑ
package com.spring_cloud.eureka.client.auth;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class AuthConfig {
// SecurityFilterChain ๋น์ ์ ์ํฉ๋๋ค. ์ด ๋ฉ์๋๋ Spring Security์ ๋ณด์ ํํฐ ์ฒด์ธ์ ๊ตฌ์ฑํฉ๋๋ค.
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// CSRF ๋ณดํธ๋ฅผ ๋นํ์ฑํํฉ๋๋ค. CSRF ๋ณดํธ๋ ์ฃผ๋ก ๋ธ๋ผ์ฐ์ ํด๋ผ์ด์ธํธ๋ฅผ ๋์์ผ๋ก ํ๋ ๊ณต๊ฒฉ์ ๋ฐฉ์งํ๊ธฐ ์ํด ์ฌ์ฉ๋ฉ๋๋ค.
.csrf(csrf -> csrf.disable())
// ์์ฒญ์ ๋ํ ์ ๊ทผ ๊ถํ์ ์ค์ ํฉ๋๋ค.
.authorizeHttpRequests(authorize -> authorize
// /auth/signIn ๊ฒฝ๋ก์ ๋ํ ์ ๊ทผ์ ํ์ฉํฉ๋๋ค. ์ด ๊ฒฝ๋ก๋ ์ธ์ฆ ์์ด ์ ๊ทผํ ์ ์์ต๋๋ค.
.requestMatchers("/auth/signIn").permitAll()
// ๊ทธ ์ธ์ ๋ชจ๋ ์์ฒญ์ ์ธ์ฆ์ด ํ์ํฉ๋๋ค.
.anyRequest().authenticated()
)
// ์ธ์
๊ด๋ฆฌ ์ ์ฑ
์ ์ ์ํฉ๋๋ค. ์ฌ๊ธฐ์๋ ์ธ์
์ ์ฌ์ฉํ์ง ์๋๋ก STATELESS๋ก ์ค์ ํฉ๋๋ค.
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
// ์ค์ ๋ ๋ณด์ ํํฐ ์ฒด์ธ์ ๋ฐํํฉ๋๋ค.
return http.build();
}
}
3. Service ์์ฑ(ํ ํฐ ๋ฐ๊ธ)
package com.spring_cloud.eureka.client.auth;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.crypto.SecretKey;
import java.util.Date;
@Service
public class AuthService {
@Value("${spring.application.name}")
private String issuer;
@Value("${service.jwt.access-expiration}")
private Long accessExpiration;
private final SecretKey secretKey;
public AuthService(@Value("${service.jwt.secret-key}") String secretKey) {
this.secretKey = Keys.hmacShaKeyFor(Decoders.BASE64URL.decode(secretKey));
}
// access token ์์ฑ
public String createAccessToken(String user_id) {
return Jwts.builder()
.claim("user_id", user_id)
.issuer(issuer) // ํ ํฐ ๋ฐ๊ธ์
.issuedAt(new Date((System.currentTimeMillis())))
.expiration(new Date(System.currentTimeMillis() + accessExpiration))
.signWith(secretKey, Jwts.SIG.HS512)
.compact();
}
}
4. Controller ์์ฑ
package com.spring_cloud.eureka.client.auth;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
public class AuthController {
private final AuthService authService;
// ResponseEntity<?>: ?๋ ์์ผ๋ ์นด๋
@GetMapping("/auth/signIn")
public ResponseEntity<?> createAuthToken(@RequestParam String user_id) {
return ResponseEntity.ok(new AuthResponse(authService.createAccessToken(user_id)));
}
@Data
@AllArgsConstructor
@NoArgsConstructor
static class AuthResponse {
private String access_token;
}
}


5. Gateway์ ํ ํฐ ์ฒดํฌ ๊ธฐ๋ฅ ์ถ๊ฐ(ํํฐ)
server:
port: 19091 # ๊ฒ์ดํธ์จ์ด ์๋น์ค๊ฐ ์คํ๋ ํฌํธ ๋ฒํธ
spring:
main:
web-application-type: reactive # Spring ์ ํ๋ฆฌ์ผ์ด์
์ด ๋ฆฌ์กํฐ๋ธ ์น ์ ํ๋ฆฌ์ผ์ด์
์ผ๋ก ์ค์ ๋จ
application:
name: gateway-service # ์ ํ๋ฆฌ์ผ์ด์
์ด๋ฆ์ 'gateway-service'๋ก ์ค์
cloud:
gateway:
server:
webflux:
routes: # Spring Cloud Gateway์ ๋ผ์ฐํ
์ค์
- id: order-service # ๋ผ์ฐํธ ์๋ณ์
uri: lb://order-service # 'order-service'๋ผ๋ ์ด๋ฆ์ผ๋ก ๋ก๋ ๋ฐธ๋ฐ์ฑ๋ ์๋น์ค๋ก ๋ผ์ฐํ
predicates:
- Path=/order/** # /order/** ๊ฒฝ๋ก๋ก ๋ค์ด์ค๋ ์์ฒญ์ ์ด ๋ผ์ฐํธ๋ก ์ฒ๋ฆฌ
- id: product-service # ๋ผ์ฐํธ ์๋ณ์
uri: lb://product-service # 'product-service'๋ผ๋ ์ด๋ฆ์ผ๋ก ๋ก๋ ๋ฐธ๋ฐ์ฑ๋ ์๋น์ค๋ก ๋ผ์ฐํ
predicates:
- Path=/product/** # /product/** ๊ฒฝ๋ก๋ก ๋ค์ด์ค๋ ์์ฒญ์ ์ด ๋ผ์ฐํธ๋ก ์ฒ๋ฆฌ
- id: auth-service # ๋ผ์ฐํธ ์๋ณ์
uri: lb://auth-service # 'auth-service'๋ผ๋ ์ด๋ฆ์ผ๋ก ๋ก๋ ๋ฐธ๋ฐ์ฑ๋ ์๋น์ค๋ก ๋ผ์ฐํ
predicates:
- Path=/auth/signIn # /auth/signIn ๊ฒฝ๋ก๋ก ๋ค์ด์ค๋ ์์ฒญ์ ์ด ๋ผ์ฐํธ๋ก ์ฒ๋ฆฌ
discovery:
locator:
enabled: true # ์๋น์ค ๋์ค์ปค๋ฒ๋ฆฌ๋ฅผ ํตํด ๋์ ์ผ๋ก ๋ผ์ฐํธ๋ฅผ ์์ฑํ๋๋ก ์ค์
eureka:
client:
service-url:
defaultZone: http://localhost:19090/eureka/ # Eureka ์๋ฒ์ URL์ ์ง์
service:
jwt:
access-expiration: 3600000
secret-key: secret-key
// build.gradle ๋ด dependencies์ ์ถ๊ฐ
implementation 'io.jsonwebtoken:jjwt:0.12.6'
package com.spring_cloud.eureka.client.gateway;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.crypto.SecretKey;
@Component
@Slf4j
public class LocalJwtAuthenticationFilter implements GlobalFilter {
@Value("${service.jwt.secret-key}")
private String secretKey;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// /auth/signIn์ผ๋ก ๋ค์ด์จ URI๋ ํํฐ ์ํ X
String path = exchange.getRequest().getURI().getPath();
if(path.equals("/auth/signIn")) {
return chain.filter(exchange);
}
// ํ ํฐ ๊ฐ์ ธ์ค๊ธฐ
String token = extractToken(exchange);
// ํ ํฐ ๊ฒ์ฆ ์คํจ
if(token == null || !validateToken(token)) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
// ํค๋์์ ํ ํฐ ์ถ์ถํ๋ ๋ฉ์๋
private String extractToken(ServerWebExchange exchange) {
String authHeader = exchange.getRequest().getHeaders().getFirst("Authorization");
if(authHeader != null && authHeader.startsWith("Bearer ")) {
return authHeader.substring(7); // Bearer ์ ์ธํ ์์ ํ ํฐ ์ถ์ถ
}
return null;
}
// ํ ํฐ ๊ฒ์ฆ
private boolean validateToken(String token) {
try {
SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64URL.decode(secretKey));
Jws<Claims> claimsJws = Jwts.parser()
.verifyWith(key)
.build().parseSignedClaims(token);
log.info("#####payload :: " + claimsJws.getPayload().toString());
// ์ถ๊ฐ์ ์ธ ๊ฒ์ฆ ๋ก์ง (์: ํ ํฐ ๋ง๋ฃ ์ฌ๋ถ ํ์ธ ๋ฑ)์ ์ฌ๊ธฐ์ ์ถ๊ฐํ ์ ์์ต๋๋ค.
return true;
} catch (Exception e) {
return false;
}
}
}



Config(Spring Cloud Config)
Spring Cloud Config๋ ๋ถ์ฐ ์์คํ ํ๊ฒฝ์์ ์ค์ ์ง์ค์ ๊ตฌ์ฑ ๊ด๋ฆฌ๋ฅผ ์ ๊ณตํ๋ ํ๋ ์์ํฌ๋ก,
์ ํ๋ฆฌ์ผ์ด์ ์ ์ค์ ์ ์ค์์์ ๊ด๋ฆฌํ๊ณ , ๋ณ๊ฒฝ ์ฌํญ์ ์ค์๊ฐ์ผ๋ก ๋ฐ์ํ ์ ์์
- Git, ํ์ผ ์์คํ , JDBC ๋ฑ ๋ค์ํ ์ ์ฅ์๋ฅผ ์ง์ํจ
- ์ค์ ์ง์ค์ ๊ตฌ์ฑ ๊ด๋ฆฌ: ๋ชจ๋ ๋ง์ดํฌ๋ก์๋น์ค์ ์ค์ ์ ์ค์์์ ๊ด๋ฆฌ
- ํ๊ฒฝ๋ณ ๊ตฌ์ฑ: ๊ฐ๋ฐ, ํ ์คํธ, ์ด์ ๋ฑ ํ๊ฒฝ๋ณ๋ก ๊ตฌ์ฑ์ ๋ถ๋ฆฌํด ๊ด๋ฆฌ
- ์ค์๊ฐ ๊ตฌ์ฑ ๋ณ๊ฒฝ: ์ค์ ๋ณ๊ฒฝ ์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฌ์์ํ์ง ์๊ณ ๋ ์ค์๊ฐ์ผ๋ก ๋ฐ์ ๊ฐ๋ฅ
Spring Cloud Config ์๋ฒ ์ค์
- build.gradle์ ์์กด์ฑ ์ถ๊ฐ
dependencies {
implementation 'org.springframework.cloud:spring-cloud-config-server'
implementation 'org.springframework.boot:spring-boot-starter-web'
}
- Spring Boot ์ ํ๋ฆฌ์ผ์ด์ ์์ Config ์๋ฒ ์ค์
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
- Config ์๋ฒ์ ์ค์ ์ ์
server:
port: 8888
spring:
cloud:
config:
server:
git:
uri: https://github.com/my-config-repo/config-repo
clone-on-start: true
Spring Cloud Config ํด๋ผ์ด์ธํธ ์ค์
- build.gradle ์์กด์ฑ ์ถ๊ฐ
dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-config'
}
- Config ์๋ฒ ์ค์ ์ ์
spring:
application:
name: my-config-client
cloud:
config:
discovery:
enabled: true
service-id: config-server
eureka:
client:
service-url:
defaultZone: http://localhost:19090/eureka/
ํ๊ฒฝ๋ณ ๊ตฌ์ฑ ๊ด๋ฆฌ
- Config ์๋ฒ๋ ํ๊ฒฝ๋ณ๋ก ๋ค๋ฅธ ์ค์ ํ์ผ์ ์ ๊ณตํ ์ ์์
- ex) application-dev.yml, application-prod.yml ํ์ผ์ Git ์ ์ฅ์์ ์ ์ฅํด ํ๊ฒฝ๋ณ ์ค์ ๊ด๋ฆฌํจ
- Spring Boot ์ ํ๋ฆฌ์ผ์ด์ ์์๋ ํ๋กํ์ ์ฌ์ฉํด ํ๊ฒฝ ๊ตฌ๋ถํ ์ ์์
// Spring Boot ์ ํ๋ฆฌ์ผ์ด์
์์ ํ๋กํ์ ์ฌ์ฉํด ํ๊ฒฝ ๊ตฌ๋ถ
spring:
profiles:
active: dev
์ค์๊ฐ ๊ตฌ์ฑ ๋ณ๊ฒฝ
์ค์๊ฐ ๊ตฌ์ฑ ๋ณ๊ฒฝ์ ๋ฐ์ํ๋ ๋ฐฉ๋ฒ์ ์ฌ๋ฌ ๊ฐ์ง ์กด์ฌํจ
- Spring Cloud Bus ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ
- ์๋์ผ๋ก /actuator/refresh ์๋ํฌ์ธํธ ํธ์ถํ๋ ๋ฐฉ๋ฒ
- Spring Boot DevTools ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ
- Git ์ ์ฅ์๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ
๊ฐ ๋ฐฉ๋ฒ์ ์ํฉ์ ๋ฐ๋ผ ์ ์ ํ ์ ํํด ์ฌ์ฉํ ์ ์์
Spring Cloud Bus
Spring Cloud Bus๋ฅผ ์ฌ์ฉํ๋ฉด ์ค์ ๋ณ๊ฒฝ ์ฌํญ์ ์ค์๊ฐ์ผ๋ก ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฐ์ ๊ฐ๋ฅํจ. ์ด๋ฅผ ์ํด์๋ ๋ฉ์์ง ์์คํ (RabbitMQ ๋๋ Kafka ๋ฑ)์ ์ฌ์ฉํ์ฌ ๋ณ๊ฒฝ ์ฌํญ์ ์ ํํด์ผ ํจ
์๋ ๊ตฌ์ฑ ๊ฐฑ์
1) /actuator/refresh ์ค๋ํฌ์ธํธ ์ฌ์ฉ
Spring Cloud Bus๋ฅผ ์ฌ์ฉํ์ง ์๋ ๊ฒฝ์ฐ, ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์๋์ผ๋ก ์ค์ ์ ๊ฐฑ์ ํ ์ ์์. ์ด๋ฅผ ์ํด์๋ Spring Actuator์ /actuator/refresh ์ค๋ํฌ์ธํธ๋ฅผ ์ฌ์ฉํ ์ ์์
- Config ์๋ฒ์์ ์ค์ ํ์ผ ๋ณ๊ฒฝ
- ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์ ์ /actuator/refresh ์ค๋ํฌ์ธํธ๋ฅผ POST ์์ฒญ์ผ๋ก ํธ์ถํด ๋ณ๊ฒฝ๋ ์ค์ ๋ฐ์
์ด ๋ฐฉ๋ฒ์ ๊ฐ๋จํ์ง๋ง, ๊ฐ ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์๋์ผ๋ก ์ค๋ํฌ์ธํธ๋ฅผ ํธ์ถํด์ผ ํจ
2) Spring Boot DevTools ์ฌ์ฉ
๊ฐ๋ฐ ํ๊ฒฝ์์ ํ์ผ ๋ณ๊ฒฝ์ ์๋์ผ๋ก ๊ฐ์งํ๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฌ์์ํ ์ ์์. ์ด๋ classpath ๋ด์ ํ์ผ ๋ณ๊ฒฝ๋ ํฌํจ๋จ
Git ์ ์ฅ์ ์ฌ์ฉ
Spring Cloud Config ์๋ฒ๊ฐ Git ์ ์ฅ์์์ ์ค์ ํ์ผ์ ์ฝ์ด์ค๋๋ก ์ค์ ํ ์ ์์. ์ด๋ ์ค์ ํ์ผ์ ๋ณ๊ฒฝ ์ฌํญ์ ์ฝ๊ฒ ๋ฐ์ํ๊ณ , ์ฌ๋ฌ ์๋น์ค ๊ฐ์ ์ผ๊ด๋ ๊ตฌ์ฑ์ ์ ์งํ๋ ๋ฐ ์ ์ฉํจ
์ค์ต
Config ์๋ฒ ์์ฑํ๊ณ product ์ ํ๋ฆฌ์ผ์ด์ ์ด local์์ ๋์ํ ๋ ํฌํธ ์ ๋ณด ๋ฐ ๋ฉ์์ง๋ฅผ Config ์๋ฒ์์ ๊ฐ์ ธ์ด
Config ์๋ฒ์ ๋ฉ์์ง๋ฅผ ๋ณ๊ฒฝํด product ์ ํ๋ฆฌ์ผ์ด์ ์ message๊ฐ ๊ฐฑ์ ๋๋ ๋ชจ์ต ํ์ธ
- ์ค์ต์์ ๋ค์ดํฐ๋ธ ๋ชจ๋ ์ฌ์ฉ
๋ก์ปฌ ํ๊ฒฝ(๋ค์ดํฐ๋ธ ๋ชจ๋)์์๋ ์ค์ ๋ณ๊ฒฝ ํ ๋ฐ์ํ๋ ค๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฌ์์ํด์ผ ํจ
ํ์ ์์๋ ์ง์ Config ์๋ฒ๋ฅผ ๊ตฌํํ๊ธฐ๋ณด๋ค๋, ์ด๋ฏธ ๊ตฌ์ถ๋ Config ์๋ฒ๋ฅผ ์ฐ๋ํด ์ฌ์ฉํ๋ ๊ฒฝ์ฐ๊ฐ ์ผ๋ฐ์ ์ผ ๊ฒ์

1. Config-server ์์ฑ ๋ฐ ํ์ ์ค์

package com.spring_cloud.eureka.client.config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableConfigServer // ์ถ๊ฐ
public class ConfigApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigApplication.class, args);
}
}
// application.yml
server:
port: 18080
spring:
profiles:
active: native
application:
name: config-server
cloud:
config:
server:
native:
search-locations: classpath:/config-repo # config-repo๋ฅผ resource ํด๋๋ก ์ง์
eureka:
client:
service-url:
defaultZone: http://localhost:19090/eureka/ # Eureka ์๋ฒ์ URL์ ์ง์
2. product์ ์ ๋ฌํ yml ํ์ผ ์ ์ธ
// resources > config-repo > product-service.yml
server:
port: 19093
message: "product-service message"
// resources > config-repo > product-service-local.yml
server:
port: 19083
message: "product-service-local message"
3. product ์๋น์ค ์์ (Config ์๋ฒ์์ ๊ฐ์ ธ์ค๊ธฐ ์ํด)
// ์์กด์ฑ ์ถ๊ฐ
implementation 'org.springframework.cloud:spring-cloud-starter-config'
// application.yml
spring:
application:
name: product-service
profiles:
active: local
config:
import: "configserver:"
cloud:
config:
discovery:
enabled: true
service-id: config-server
server:
port: 0
eureka:
client:
service-url:
defaultZone: http://localhost:19090/eureka/
message: "default message"
package com.spring_cloud.eureka.client.product;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ProductController {
// application.properties์ ์์ฑํ server.port ๊ฐ์ ธ์ค๊ธฐ
@Value("${server.port}")
private String serverPort; // ์ ํ๋ฆฌ์ผ์ด์
ํฌํธ
@Value("${message}")
private String message;
@GetMapping("/product")
public String getProduct() {
return "Product info!!! From port :" + serverPort + "and message : " + message;
}
}


4. Config ๋ฉ์์ง ๋ณ๊ฒฝ ์ Product ์๋น์ค์๋ ๋ฉ์์ง ๋ณ๊ฒฝ ๋ฐ์
// product ์๋น์ค application.yml ํ์ผ์ refresh ์ถ๊ฐ
...
management:
endpoints:
web:
exposure:
include: refresh
message: "default message"
package com.spring_cloud.eureka.client.product;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RefreshScope // value๋ฅผ ๊ฐ์ ๊ฐ์ ธ์ ์ฌ์ฉ ์ค์ด๊ธฐ ๋๋ฌธ์, refresh ๋ณ๊ฒฝ ์ ํด๋น ๊ฐ๋ ์
๋ฐ์ดํธ
@RestController
public class ProductController {
// application.properties์ ์์ฑํ server.port ๊ฐ์ ธ์ค๊ธฐ
@Value("${server.port}")
private String serverPort; // ์ ํ๋ฆฌ์ผ์ด์
ํฌํธ
@Value("${message}")
private String message;
@GetMapping("/product")
public String getProduct() {
return "Product info!!! From port :" + serverPort + "and message : " + message;
}
}

- 'http://localhost:19083/actuator/refresh' ์๋ ์คํ ํ, ์ ๋ฐ์ดํธ ๋ฐ์ ํ์ธ

๋ถ์ฐ ์ถ์ (Spring Cloud Sleuth) ๋ฐ ๋ก๊น (Zipkin)
๋ถ์ฐ ์ถ์ ์ ๋ถ์ฐ ์์คํ ์์ ์๋น์ค ๊ฐ์ ์์ฒญ ํ๋ฆ์ ์ถ์ ํ๊ณ ๋ชจ๋ํฐ๋งํ๋ ๋ฐฉ๋ฒ์ผ๋ก, ๊ฐ ์๋น์ค์ ํธ์ถ ๊ด๊ณ์ ์ฑ๋ฅ์ ์๊ฐํํด ๋ฌธ์ ๋ฅผ ์ง๋จํ๊ณ ํด๊ฒฐํ ์ ์๋๋ก ๋์
- ์ฃผ์ ๊ฐ๋ : ํธ๋ ์ด์ค(Trace), ์คํฌ(Span), ์ปจํ ์คํธ(Context)
- ๋ง์ดํฌ๋ก์๋น์ค ์ํคํ ์ฒ์์ ์ฌ๋ฌ ์๋น์ค๊ฐ ํ๋ ฅํด ํ๋์ ์์ฒญ์ ์ฒ๋ฆฌํจ
- ์๋น์ค ๊ฐ์ ๋ณต์กํ ํธ์ถ ๊ด๊ณ๋ก ์ธํด ๋ฌธ์ ๋ฐ์ ์ ์์ธ์ ํ์ ํ๊ธฐ ์ด๋ ค์ธ ์ ์์
- ๋ถ์ฐ ์ถ์ ์ ํตํด ๊ฐ ์๋น์ค์ ํธ์ถ ํ๋ฆ์ ๋ช ํํ ํ์ ํ๊ณ , ์ฑ๋ฅ ๋ณ๋ชฉ์ด๋ ์ค๋ฅ๋ฅผ ๋น ๋ฅด๊ฒ ์ง๋จ ๊ฐ๋ฅ
Micrometer
Spring ๊ธฐ๋ฐ ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋งคํธ๋ฆญ์ ์์งํ๊ณ ๋ชจ๋ํฐ๋งํ๊ธฐ ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก,
๊ฐ ์๋น์ค์ ์ฑ๋ฅ ์งํ๋ฅผ ์์งํ๊ณ , Prometheus, Grafana ๋ฑ๊ณผ ์ฐ๋ํด ์๊ฐํ ๊ฐ๋ฅํจ
๋ถ์ฐ ์ถ์ ์ ์ํ ๊ธฐ๋ฅ๋ ์ ๊ณตํด ์๋น์ค ๊ฐ์ ํธ์ถ ํ๋ฆ์ ์ถ์ ํ ์ ์์
- ๋ค์ํ ๋งคํธ๋ฆญ ์์ง: ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ค์ํ ์ฑ๋ฅ ์งํ ์์ง
- ์ ์ฐํ ์ฐ๋: Prometheus, Grafana ๋ฑ ๋ค์ํ ๋ชจ๋ํฐ๋ง ๋๊ตฌ์ ์ฐ๋ ๊ฐ๋ฅ
- ์ถ์ ๊ธฐ๋ฅ: ์๋น์ค ๊ฐ์ ํธ์ถ ํ๋ฆ์ ์ถ์ ํด ์ฑ๋ฅ ๋ณ๋ชฉ ์ง๋จ ๊ฐ๋ฅ
Zipkin
ํธ๋ ์ด์ค ๋ฐ์ดํฐ๋ฅผ ์์งํ๊ณ ์๊ฐํํ๋ ๋ถ์ฐ ์ถ์ ์์คํ ์ผ๋ก, ๊ฐ ์๋น์ค์ ํธ๋ ์ด์ค์ ์คํฌ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๊ณ , ์ด๋ฅผ ํตํด ํธ์ถ ํ๋ฆ์ ์๊ฐํํจ
- ๋ฐ์ดํฐ ์์ง ๋ฐ ์ ์ฅ: ๊ฐ ์๋น์ค์์ ์ ์ก๋ ํธ๋ ์ด์ค ๋ฐ์ดํฐ๋ฅผ ์์งํ๊ณ ์ ์ฅ
- ์๊ฐํ: ํธ๋ ์ด์ค ๋ฐ์ดํฐ๋ฅผ ์๊ฐํํด ์๋น์ค ๊ฐ์ ํธ์ถ ๊ด๊ณ๋ฅผ ๋ช ํํ ํ์ ๊ฐ๋ฅ
- ๊ฒ์ ๋ฐ ํํฐ๋ง: ํน์ ํธ๋ ์ด์ค๋ ์คํฌ์ ๊ฒ์ํ๊ณ ํํฐ๋งํด ๋ฌธ์ ์ง๋จ ๊ฐ๋ฅ
์๋ฒ ์ค์
- Zipkin ์๋ฒ๋ Docker๋ฅผ ์ฌ์ฉํด ์คํํ ์ ์์
docker run -d -p 9411:9411 openzipkin/zipkin
๋ถ์ฐ ์ถ์ ์์
1. ์๋น์ค ํธ์ถ ํ๋ฆ ์ถ์
- ์๋น์ค ๊ฐ์ ํธ์ถ ํ๋ฆ์ ์ถ์ ํ๊ณ , Zipkin ๋์๋ณด๋์์ ์๊ฐํ
- ๊ฐ ์๋น์ค ํธ์ถ ์ ํธ๋ ์ด์ค์ ์คํฌ์ด ์์ฑ๋๊ณ , Zipkin ์๋ฒ๋ก ์ ์ก๋จ
2. ์ฑ๋ฅ ๋ณ๋ชฉ ์ง๋จ
- Zipkin ๋์๋ณด๋๋ฅผ ํตํด ์ฑ๋ฅ ๋ณ๋ชฉ์ด ๋ฐ์ํ๋ ๋ถ๋ถ ์๋ณ
- ๊ฐ ์คํฌ์ ์์ ์๊ฐ๊ณผ ํธ์ถ ๊ด๊ณ๋ฅผ ๋ถ์ํด ์ฑ๋ฅ ๋ฌธ์ ๋ฅผ ์ง๋จํ๊ณ ํด๊ฒฐ

์ด๋ฒคํธ ๋๋ฆฌ๋ธ ์ํคํ ์ฒ์ ์คํธ๋ฆผ ์ฒ๋ฆฌ(Spring Cloud Stream)
์ด๋ฒคํธ ๋๋ฆฌ๋ธ ์ํคํ ์ฒ๋ ์์คํ ์์ ๋ฐ์ํ๋ ์ด๋ฒคํธ(์ํ ๋ณํ๋ ํ๋)๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋์ํ๋ ์ํํธ์จ์ด ์ค๊ณ ์คํ์ผ์.
์ด๋ฒคํธ๋ ๋น๋๊ธฐ์ ์ผ๋ก ์ฒ๋ฆฌ๋๋ฉฐ, ์๋น์ค ๊ฐ์ ๋์จํ ๊ฒฐํฉ์ ํตํด ๋ ๋ฆฝ์ ์ผ๋ก ๋์ํ ์ ์๊ฒ ํจ
- ์ด๋ฒคํธ: ์์คํ ๋ด์์ ๋ฐ์ํ๋ ์ํ ๋ณํ๋ ํ๋์ ๋ํ๋ด๋ ๋ฉ์์ง
- ์ด๋ฒคํธ ์์ค: ์ด๋ฒคํธ๋ฅผ ์์ฑํด ์ด๋ฒคํธ ๋ฒ์ค์ ์ ๋ฌํ๋ ์ญํ
- ์ด๋ฒคํธ ํธ๋ค๋ฌ: ์ด๋ฒคํธ๋ฅผ ์์ ํด ์ฒ๋ฆฌํ๋ ์ญํ
- ์ด๋ฒคํธ ๋ฒ์ค: ์ด๋ฒคํธ ์์ค์ ์ด๋ฒคํธ ํธ๋ค๋ฌ ๊ฐ์ ๋ฉ์์ง ์ ๋ฌ์ ์ค๊ณ
์ฅ์
1. ๋์จํ ๊ฒฐํฉ
- ์๋น์ค ๊ฐ์ ๊ฐํ ์ข ์์ฑ์ ์ ๊ฑฐํด ๋ ๋ฆฝ์ ์ธ ๊ฐ๋ฐ๊ณผ ๋ฐฐํฌ ๊ฐ๋ฅ
- ์ด๋ฒคํธ ๊ธฐ๋ฐ ํต์ ์ ํตํด ์๋น์ค ๊ฐ์ ๊ฒฐํฉ๋ ๋ฎ์ถค
2. ํ์ฅ์ฑ
- ์ํ ํ์ฅ์ด ์ฉ์ดํ์ฌ ๋๊ท๋ชจ ์์คํ ์์ ์ ์ฉ
- ์ด๋ฒคํธ ํ๋ก๋์์ ์ปจ์๋จธ๋ฅผ ๋ ๋ฆฝ์ ์ผ๋ก ํ์ฅ ๊ฐ๋ฅ
3. ๋น๋๊ธฐ ์ฒ๋ฆฌ
- ์ด๋ฒคํธ๋ฅผ ๋น๋๊ธฐ์ ์ผ๋ก ์ฒ๋ฆฌํด ์์คํ ์ ์๋ต์ฑ ํฅ์
- ์์ฒญ๊ณผ ์๋ต์ ๋น๋๊ธฐ์ ์ผ๋ก ์ฒ๋ฆฌํด ์ฑ๋ฅ ์ต์ ํ
๋จ์
1. ๋ณต์ก์ฑ ์ฆ๊ฐ
- ์ด๋ฒคํธ ๊ธฐ๋ฐ ํต์ ์ผ๋ก ์ธํด ์์คํ ์ ๋ณต์ก์ฑ ์ฆ๊ฐ
- ์ด๋ฒคํธ ํ๋ฆ๊ณผ ์ํ ๊ด๋ฆฌ๋ฅผ ์ฒด๊ณ์ ์ผ๋ก ์ค๊ณ ํ์
2. ์ฅ์ ์ ํ
- ์ด๋ฒคํธ ์คํจ ์ ๋ค๋ฅธ ์๋น์ค๋ก ์ฅ์ ๊ฐ ์ ํ๋ ์ ์์
- ์ด๋ฒคํธ ์ฌ์ฒ๋ฆฌ ๋ฐ ์ฅ์ ๋ณต๊ตฌ ๋ฉ์ปค๋์ฆ ๊ตฌํ ํ์
์์: ์จ๋ผ์ธ ์ผํ๋ชฐ
1. ์ด๋ฒคํธ ์์ค: ์ฌ์ฉ์๊ฐ ์จ๋ผ์ธ ์ผํ๋ชฐ์์ ์ฃผ๋ฌธ
- ์ฃผ๋ฌธ ์๋น์ค๊ฐ '์ฃผ๋ฌธ ์์ฑ' ์ด๋ฒคํธ ๋ฐ์์ํด
2. ์ด๋ฒคํธ ๋ฒ์ค: Kafka๋ RabbitMQ์ ๊ฐ์ ๋ฉ์์ง ๋ธ๋ก์ปค๊ฐ '์ฃผ๋ฌธ ์์ฑ' ์ด๋ฒคํธ๋ฅผ ์ ๋ฌ
3. ์ด๋ฒคํธ ํธ๋ค๋ฌ:
- ์ฌ๊ณ ์๋น์ค: '์ฃผ๋ฌธ ์์ฑ' ์ด๋ฒคํธ๋ฅผ ์์ ํด ์ฌ๊ณ ๋ฅผ ํ์ธํ๊ณ ์ ๋ฐ์ดํธ
- ๋ฐฐ์ก ์๋น์ค: '์ฃผ๋ฌธ ์์ฑ' ์ด๋ฒคํธ๋ฅผ ์์ ํด ๋ฐฐ์ก ์ค๋น ์์
- ๊ฒฐ์ ์๋น์ค: '์ฃผ๋ฌธ ์์ฑ' ์ด๋ฒคํธ๋ฅผ ์์ ํด ๊ฒฐ์ ์ฒ๋ฆฌ
Spring Cloud Stream
์ด๋ฒคํธ ๋๋ฆฌ๋ธ ๋ง์ดํฌ๋ก์๋น์ค๋ฅผ ๊ตฌ์ถํ๊ธฐ ์ํ ํ๋ ์์ํฌ๋ก, Kafka๋ RabbitMQ ๋ฑ์ ๋ฉ์์ง ๋ธ๋ก์ปค์ ํตํฉํ์ฌ ์ด๋ฒคํธ ์คํธ๋ฆฌ๋ฐ์ ์ฒ๋ฆฌํจ
- Producer-Consumer ๊ฐ์ ํต์ ์ ์ถ์ํํด ๊ฐํธํ๊ฒ ์ด๋ฒคํธ ๊ธฐ๋ฐ ์ ํ๋ฆฌ์ผ์ด์ ๊ฐ๋ฐ ๊ฐ๋ฅ
- ๋ฐ์ธ๋ ์ถ์ํ: ๋ฉ์์ง ๋ธ๋ก์ปค์์ ํตํฉ์ ์ํ ์ถ์ํ ๋ ์ด์ด ์ ๊ณต
- ํ๋ก๋์/์ปจ์๋จธ ๋ชจ๋ธ: ์ด๋ฒคํธ๋ฅผ ์์ฑํ๊ณ ์ฒ๋ฆฌํ๋ ํ๋ก๋์์ ์ปจ์๋จธ ๋ชจ๋ธ ์ง์
- ์ ์ฐํ ์ค์ : ๋ค์ํ ์ค์ ์ต์ ์ ํตํด ์์ฝ๊ฒ ์ปค์คํฐ๋ง์ด์ง ๊ฐ๋ฅ

์ฟ ๋ฒ๋คํฐ์ค(Kubernetes)
์ฟ ๋ฒ๋คํฐ์ค(Kubernetes)๋ ์ปจํ ์ด๋ํ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฐฐํฌ, ํ์ฅ, ์ด์์ ์๋ํํ๋ ์คํ์์ค ํ๋ซํผ์ผ๋ก, Google์์ ๊ฐ๋ฐํ๊ณ ํ์ฌ๋ CNCF(Cloud Native Computing Foundation)์์ ๊ด๋ฆฌํจ
- ์ปจํ ์ด๋ ์ค์ผ์คํธ๋ ์ด์ ๋๊ตฌ๋ก, ๋ค์์ ์ปจํ ์ด๋๋ฅผ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌ ๊ฐ๋ฅ
- ์ปจํ ์ด๋: ์ ํ๋ฆฌ์ผ์ด์ ๊ณผ ๊ทธ ์ข ์์ฑ์ ํจ๊ป ํจํค์งํ ๊ฐ์ํ๋ ํ๊ฒฝ
- Pod: ์ฟ ๋ฒ๋คํฐ์ค์์ ์คํ๋๋ ์ต์ ๋จ์์ ๋ฐฐํฌ ๊ฐ์ฒด๋ก, ํ๋ ์ด์์ ์ปจํ ์ด๋๋ฅผ ํฌํจ
- ๋ ธ๋(Node): ์ฟ ๋ฒ๋คํฐ์ค ํด๋ฌ์คํฐ์์ Pod๊ฐ ์คํ๋๋ ๋ฌผ๋ฆฌ์ ๋๋ ๊ฐ์ ๋จธ์
- ํด๋ฌ์คํฐ: ์ฌ๋ฌ ๋ ธ๋๋ก ๊ตฌ์ฑ๋ ์ฟ ๋ฒ๋คํฐ์ค์ ์งํฉ
- ๋ค์์คํ์ด์ค(Namespace): ํด๋ฌ์คํฐ ๋ด์์ ๋ฆฌ์์ค๋ฅผ ๋ ผ๋ฆฌ์ ์ผ๋ก ๊ตฌ๋ถํ๋ ๋จ์