2019년 10월 7일 월요일

서버 개발자로 살아가기 #3. Kubernetes로 배포되는 MSA 시스템에서 프로그래밍을 할 때 알아두면 좋은 점들



앞선 글에서 간단하게 MSA에 대한 개념 정리를 해 보았다.

MSA 구조가 가져다 주는 장점들이 많이 있지만, 반면에 서비스의 종류와 갯수가 많아지고 서비스 간 통신 구조가 복잡해 진다는 단점도 존재한다.


시스템 구조가 복잡해 짐에 따라, 자원의 효율적이고 유연한 "관리"의 필요성이 커졌다.


쉽게 말하면 어떤 서비스는 자원을 조금만 할당해도 되고 어떤 서비스는 자원을 많이 할당해 줘야 한다는 뜻이다.


컴퓨팅 파워가 충분히 좋아져서 단일 머신의 자원 사용을 얼마나 최적화 하는지 여부는 상대적으로 덜 중요해졌다. VM(virtualization), hypervisor, container 기술들은 이런 시대상을 반영하여 발전하였다.

다시 한번 강조하지만, 하드웨어 자원은 이제 정적이고 물리적인 개념으로 인지하는 것이 아니라 추상적인 Resource 단위로 인식하고 사용한다. AWS 같은 클라우드 서비스를 사용할 때 우리는 instance 라는 용어를 사용한다.


사실 이런 고민들을 해결하기 위한 기술 발전의 역사는 꽤 오래되었다. 20세기에는 하드웨어 자원이 너무 비싸서 하나의 워크스테이션에 여러 Terminal 을 붙여 공동으로 자원을 공유하던 시절도 있긴 하다. 그 당시에는 투자 대비 최고의 효율을 낼 수 있는 방법이었고, 이제 다시 자원을 빌려 쓰는 시대가 왔다. 자원을 논리적으로 나눠 쓰는 개념은 절대 새로운 것이 아니다. 역사는 돌고 돌아 계속 기술이 변화하고 발전했을 뿐.. 그리고 그 목적이 조금씩 달라졌을 뿐이다.


가장 중요하게 이해해야 하는 기술은 container 이다. (정확히는 Linux Container)
container 기술은 OS의 low level 자원들을 공유하며 어플리케이션 레이어와 시스템 라이브러리 레벨만 패키징을 하여 매우 가벼운 단위로 시스템을 추상화 할 수 있게 해준다.
Host OS 레벨까지 가상화를 하는 VM 보다 훨씬 자원 효율성도 좋고 어느정도 필요한 수준의 추상화가 가능하기 때문에 이제 웬만한 서버 어플리케이션은 컨테이너화 하여 배포하는 것이 보편화 되었다. 가장 널리 보편화된 컨테이너는 다들 알고 있는 Docker가 있다.


Kubernetes는 container-orchestration system 이다.
다양한 컨테이너를 많이 관리하기 위한 기술로 구글이 혼자 몰래 쓰다가 오픈소스로 공개 되면서 또 하나의 테크 스택에 한 획을 그었다.
(전 세계에서 동일한 수준의 서비스를 배포 관리해야 했던 구글의 역사를 보면 일찍이 Kubernetes 같은 기술이 필요할 수 밖에 없다.)

다양한 종류의 악기 연주자(서비스)들이 여럿 있다.
바이올린이나 첼로 연주자는 많이 있고
트럼펫 같은 연주자는 한두명 밖에 없다
지휘자가 생각하기에 바이올린 연주자가 많이 필요하다면 더 배치할 수도 있고
첼로 연주자가 별로 필요 없다면 조금 뺄 수도 있다



클라우드가 아니라 On-premise 환경은요..?
이미 On-premise 환경에서도 컨테이너와 Kubernetes가 빠르게 확산되고 보편화 되었다.
이미 많이 알고 있겠지만, 게임 회사 같은 경우 반응 속도와 같은 요구사항들이 중요하기 때문에 아직도 자체 IDC 에서 서비스를 많이 한다. (사실 비용 절감이 더 큰 이유지만..)
금융권이나 대형 제조업에서도 보안 이슈 때문에 클라우드 서비스를 활용하는 부분은 극히 제한적이다.
On-premise와 Cloud service를 동시에 병행하는 하이브리드 방식도 많이 채택중이라고 한다. 그래서 Container와 Kubernetes 기반 배포 환경이 널리 활용될 수 밖에 없다.



Kubernetes는 컨테이너를 관리하므로 Kubernetes로 배포 운영관리를 한다는 이야기는 모든 어플리케이션은 컨테이너화 되어야 한다는 뜻이다.

어플리케이션이 Container화 되어 관리되기 때문에 개발하는 어플리케이션 프로그래밍 패러다임도 바뀌어야 한다.

보통 Software Developer 들은 Feature의 구현 및 논리적 안정성, 테스트에 집중을 하기 때문에 어플리케이션이 어떤 환경에서 배포되는지는 신경쓰지 않는다.

원칙적으로는 신경 쓸 필요도 없고 신경쓰면 안된다고 하고 싶지만 현실은 배포 환경에 따라 고려해주는 것이 좋다


개인적으로 모든 서버 개발자가 한번쯤은 라이브 운영의 경험을 겪어 보도록 전사적인 정책을 정해야 한다고 생각한다. 가령 점검이나 서비스 배포, 업그레이드 시에 개발팀이 돌아가면서 참관을 한다거나..


MSA 구조, 그리고 이것을 구현하기 위해 Kubernetes 기반 배포 환경에서 (container화 된)서버 어플리케이션을 프로그래밍을 할때 알아두면 좋은 점들, 그리고 자주 범하는 실수들과 주의해야 할 점들을 아래에 정리해 보았다.




K8S, 컨테이너, 그리고 자원 관리


K8S 운영 환경에서 가용 자원은 추상화 되어 있다

컨테이너로 어플리케이션을 패키징 한다는 뜻은 가용 자원이 '추상화' 되어 있다는 뜻이다.

보통 K8S(Kubernetes)로 운영을 할 때에 단일 포드에 할당하는 가용 자원의 한계를 "고정"하도록 설계한다.

포드의 가용 자원을 제한하는 이유는 자원 사용량을 예측하기 좋고 관리하기 좋기 때문이다.

물론, K8S에서 포드의 자원을 제한 하지 않을수도 있다. (K8S는 자동으로 포드의 사용 자원량이 늘어나면 자동으로 자원을 할당해 준다.  그래도 노드에 남는 자원이 없다면 포드를 다른 노드에 배치 한다.

하지만 기본적으로 자원 관리를 위해 각 포드의 자원 사용량을 제한한다.
예를 들면 이렇게.. (K8S 에 대한 설명은 생략하겠다)
apiVersion: v1
kind: Pod
metadata:
  name: frontend
spec:
  containers:
  - name: db
    image: mysql
    env:
    - name: MYSQL_ROOT_PASSWORD
      value: "password"
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"
  - name: wp
    image: wordpress
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"
ref: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/


물론 조금만 경험있는 개발자라면 무리하게 메모리를 할당해서 배려 없이(?) 개발하지는 않는다. 실수가 발생하는 이유는 보통 이미 한번 배포된 환경에서 개발을 하기 때문에 기능을 추가하면서 늘어나는 자원에 대해 미처 신경을 쓰지 못한다는 것이다.


사실 개발자가 운영 환경을 신경 쓰지 않고 개발해야 하는 것이 이상적이긴 하지만 운영 환경의 자원이 추상화 되어 있고 고정 되어 있다는 사실을 인지하고, 그 자원 한도 안에서 프로그램의 가용성을 보장해 주도록 개발하는 것은 개발자의 역할이다.

현재 (라이브 서비스 중인 환경에) 내가 개발하고 있는 서버에 고정되어 있는 가용 자원이 어느정도 수준인지 인지하는 것은 좋은 개발 가이드가 될 수 있다.

어쩔 수 없이 가용 자원이 늘어나야 할 필요가 있을때는 이를 운영팀에 알려주어야 한다.
운영팀과 커뮤니케이션을 하는 것을 두려워 하지 말자


때로는 서비스가 점점 커지고 사용하는 자원이 지나치게 많아지면 특정 기능을 분리해서 따로 서비스로 쪼개는 설계 변경을 하기도 한다.



서비스 (컨테이너)가 뜨는 순서는 보장할 수 없다.

어떤 서비스든 간에 다른 서비스에 의존성이 생길수 밖에 없다.

A 라는 서비스가 기능을 온전히 마치려면 B 라는 서비스가 필요하고, 다시 B는 C와 D라는 서비스가 필요할 수도 있는 법이다.

문제는 이 서비스들이 착하게 D, C, B, A 순서대로 실행되어 주지 않는다는 점이다.

따라서 어떤 어플리케이션도 자신이 필요한 서비스 또는 가용 자원이 먼저 준비되어 있을 것이라는 생각을 하면 안된다.

그런 상황들을 염두에 두고 프로그램을 설계를 해야 한다.





컨텍스트와 에러 처리


기능 단위가 아닌 컨텍스트 단위로 생각하라, 그리고 가능한 컨텍스트를 단순하게 설계하라


서비스를 잘게 쪼개고 필요에 따라 Scale out을 하도록 설계한다. 는 말에는 굉장히 많은 의미가 함축되어 있다.

기능을 작게 쪼개서 서비스를 나눴다면, 하나의 요청 처리에 대한 컨텍스트를 여러 서비스가 접근, 공유해야 한다는 사실을 명시하고 프로그래밍을 해야 한다.

컨텍스트란..?
In computer science, a task context is the minimal set of data used by a task (which may be a process or thread)
여기에서는 프로세스, 스레드가 아닌 여러 서비스들 간에 사용되는 데이터 셋을 말한다.
https://en.wikipedia.org/wiki/Context_(computing)

What is the term of context
https://stackoverflow.com/questions/6145091/the-term-context-in-programming



어떤 특정 기능을 완전히 수행하기 위해서는 한번의 클라이언트 요청(컨텍스트)이 발생했을 때 여러 서비스들이 순차적으로 또는 무작위로 수행될수도 있다. MSA 구조에서 HTTP 요청 하나를 하나의 서버가 모두 처리하는 일은 드물다.

가령 로그인을 예를 들면 /login HTTP 요청이 들어왔을때 게이트 웨이 "서버"는 최초로 로그인을 처리하는 서버로 요청을 넘긴다. ID/PW를 검증하고 나면, 세션 정보를 처리하는  서버로 컨텍스트는 이어진다. 세션 정보는 Redis 메모리 캐시에 임시로 저장이 되며, 필요에 따라 DB에 접근하여 유저 정보를 업데이트를 하는 서비스가 있을수도 있다. 이런 일련의 과정이 에러 없이 모두 완료되어야 로그인 기능은 비로소 끝나게 된다.


하나의 컨텍스트를 수행할 때 수많은 서비스 들이 그 컨텍스트를 공유하며 서비스가 많아질 수록 시스템 복잡도가 증가한다


하나의 컨텍스트를 놓고 여러 분산된 서비스들이 나눠서 처리를 하기 때문에 이전 단계에서 수행했던 내용을 기억했다가 다음 단계 서비스에서 처리를 해야 할 필요가 있다. 그래서 보통 중앙집중화된 Redis 같은 메모리 캐시나 Database에 데이터를 저장하고 공유한다.

이런 패러다임으로 프로그램(서버)를 설계하는 것을 Stateless 라 부른다는 것은 다들 알고 있을 것이다.

가장 좋은 것은 하나의 요청 처리 과정에 의존하는 서비스 수를 최소화 하고 컨텍스트를 최대한 단순하게 설계하는 것이 좋다.



로직 에러가 발생하면 컨텍스트를 복구하고 즉시 에러를 리턴하라

보통 로직 에러가 명확하게 발생하면 거기에서 진행을 멈추고 에러를 리턴한다.

여기서 중요한 것은 컨텍스트를 사용 이전 상태로 복구를 해야 하는지 여부를 확인해야 한다는 것이다. 하지만 로직 에러가 났을때 단순히 에러를 throw 하고는 잊어 버리기 쉽다.



I/O 에러 발생시 재시도를 하지 말아라


간혹 에러가 발생했을 때 계속 재시도를 하는 반복문 로직을 넣기도 하는데 그다지 권장하지는 않는다.


네트워크의 불확실성으로 인해 어쩔 수 없이 재시도를 해야 하는 경우도 있다. 이럴 경우 재시도를 무한대로 하지 말고 횟수에 제한을 두고 적절한 시점에 에러를 리턴하는 것이 좋다.


단일 서비스 내에서 에러가 발생했을때 재시도를 해서 성공을 하게 되면 시스템의 Failover를 잘 해결해 줬다고 생각하기 쉽다.
하지만 전체 시스템의 퀄리티를 떨어뜨릴 뿐 그다지 큰 효율을 가지기 쉽지 않다.

그 이유는 아래와 같다.

첫째,
I/O 에러가 나면 보통의 경우에는 복구가 안되는 크리티컬한 경우가 많다.
따라서 재시도를 하더라도 결국에는 에러로 판명될 가능성이 크다.

둘째,
어차피 대부분 클라이언트 또는 다른 서비스들이 요청을 하고 나서 제시간에 응답이 오지 않으면 알아서 접속을 종료하고 에러로 간주한다.

보통 I/O 요청은 timeout 을 갖는다. 타임아웃이 10초라고 했을 때 재시도를 하는 횟수만큼 요청에 대한 응답을 주기 까지의 시간은 늘어 난다. 그 동안 클라이언트는 에러로 간주하고 새로운 요청을 생성할 가능성이 크다. (여기서 타임아웃을 튜닝하겠다는 발상은 최악이다)

셋째,
문제가 발생한 특정 시스템의 안정성 상태를 파악하기 좋다. 운좋게 재시도를 한 로직 덕분에 요청 성공률이 늘어났다고 가정하자. 과연 그 서비스는 안정적인 것으로 간주하고 workaround 를 허용해야 할 것인가? (하는 고민이 생기게 된다.)
차라리 잦은 I/O 에러가 발생하는 원인을 명확하게 인지하도록 하여 그 빈도를 알려주고, 에러의 원인을 제거하는 방향으로 문제를 해결하는 것이 맞다.

넷째,
재시도를 많이 시도하는 만큼 특정 요청이 그만큼 자원 선점을 오래 하게 된다. 따라서 서버의 자원 효율이 떨어진다. 그만큼 다른 클라이언트의 요청에 자원을 배분할 기회를 박탈하기 때문이다. 차라리 빠르게 실패하고 자원을 돌려주고 클라이언트의 요청을 처리해 주는 것이 Fair 하다!


물론 에러에 대한 처리를 어떻게 하느냐는 각 서비스 및 시스템 구조 특성상 많이 달라질 수 있으므로 이견이 많을 수 있다. 다만, 끝없이 재시도를 하거나 상태 체크를 하는 루프의 사용은 어떠한 결과를 불러올 지 모르기 때문에 자제하도록 하자



통합 테스트를 할 때에는 서비스를 여러개 띄워라


개발 타임시 단일 로직 검증을 할 때는 보통 하나의 프로세스(또는 컨테이너를) 띄워 기능 검증을 한다. 이마저도 대부분 Unit test 로 많은 부분들이 커버 된다.

MSA 구조로 설계된 시스템에서 하나의 서비스를 테스트 할 때 로컬 환경에서 단일 컨테이너를 띄워두고 런타임 테스트를 통해 확인할수 있는 것은 극히 일부분이다.

보통은 통합테스트 환경에서 수정된 서버를 수시로 배포하면서 End to End 테스트를 수행하는데, 이때 서비스를 하나씩만 띄우는 실수를 범하기도 한다.

앞서 프로그램 설계 시 컨텍스트를 단위로 생각을 하라고 하였다. 통합 테스트 환경은 여러 서비스를 띄워서 분산된 서비스들이 컨텍스트를 제대로 유지하고 로직을 완료 하는지 여부를 검증하는 단계이다.





프로그램 라이프 사이클


회복 불가능한 문제가 발생하거나 필수 자원이 사용 불가능한 경우라면 프로그램을 바로 종료 시켜라


우리는 항상 에러 및 예외 처리에 대한 중요성과 함께 프로세스의 라이프 사이클을 최대한 길게 유지하도록 안정적인 프로그램을 만들도록 훈련 받곤 했다.
과거에 서버 프로그램은 단순한 모놀리딕(Monolithic) 구조를 갖고 있었기 때문에 서버 프로그램의 종료는 곧 서비스 중단을 의미하기도 했다.
End user가 사용하는 어플리케이션의 경우 에러로 인한 프로그램의 종료는 유저 경험에 매우 안좋은 영향을 미치기 때문에 최대한 프로그램이 죽지 않도록 조심한다.


하지만 대체 가능한 서비스 들이 많이 있는 환경에서는 애매 모호한 에러로 인해 halted 상태로 빠지는 서비스는 차라리 죽여주는 것이 좋다.


특히 필수 자원 사용이 불가능한 경우에는 과감하게 시스템을 종료 시켜야 한다.
예를 들면 특정 기능에 DB 접근이 필요한 서비스가 있다고 가정하자. DB connection이 계속 맺어지지 않는 상황이 발생하면 그 서비스는 어떤 요청을 해도 에러만 뿜어내는 쓸모없는 서비스가 될 것이다. (DB 접근이 안되니까 모든 로직은 Fail 처리 될 것이다.)

이러한 상황은 특히 서비스를 모두 내렸다가 다시 올리는 점검 상황에 많이 발생하는데, 동시에 수많은 서버 인스턴스들이 startup 을 하면서 DB, Redis, Message Queue 같은 서비스들에 동시에 접속을 시도 한다. 이 때 Connection 을 자꾸 재시도를 하는 것 보다는 과감하게 프로세스를 죽이면 K8S 가 알아서 자원을 정리하고 재시도를 할 것이다.


K8S로 관리되는 시스템은 서비스의 라이프 사이클을 어느정도 K8S에 맡기는 것이 핵심이다

자원을 할당하는 것 만큼 자원을 해제하는 것은 매우 중요하다.
그것이 메모리 이건, 프로세스 이건, 컨테이너 이건



참고로 K8S 에서는 컨테이너의 liveness, readiness 여부를 체크할 수 있는 기능이 있다.
특정 API 를 주기적으로 호출해서 응답이 없는 포드는 재시작을 해서 자체 recovery를 수행하도록 구현할 수도 있다.

이런 기능을 추가하지 않았다면 프로세스를 그 즉시 '과감하게' 종료 시키는 것이 낫다.

SIGTERM handler를 구현하고 특정 에러가 발생한 원인을 로그로 남기고 프로세스를 종료하기만 해도 운영 입장에서 상황 파악을 하고 안정적으로 서비스를 유지하기에 훨씬 수월하다.


프로세스를 종료하면 Host OS에 있는 도커 매니지먼트 시스템이 이를 감지하고 자원을 정리한다. K8S는 이를 모니터링하고 있다가 서비스를 다시 살린다. K8S 는 운영자가 설계한 수 만큼 포드수를 항상 자동으로 유지한다.



자비 없는 K8S가 쓸모없는(죽은) 서비스를 정리하고 있다. K8S 스케쥴러는 비어있는 자리를 채우기 위해 새로운 포드를 띄울 것이다.


특정 서비스 하나의 안정성을 극대화 하는 것 보다, 전체 서비스의 Stability, 그리고 Visibility(문제가 생겼을 때 에러 시점 및 위치를 확인 가능 여부)를 확보하는 것이 더 중요하다.

조용히 문제를 일으키는 좀비 서비스가 남아 있으면 (자원 낭비는 둘째치고) 문제 파악 및 해결에 악영향을 미친다






프로그램이 언제 죽어도 이상하지 않다는 사실을 기억하라

회복 불능 상태로 빠지면 프로세스를 죽이라고 했다. 그 말은 프로그램 설계 시에 언제 죽어도 항상 괜찮아야 한다는 뜻이다.

특정 기능(컨텍스트)를 수행하다 프로세스를 죽이고 다른 서비스가 동일한 기능을 재시작을 해도 문제가 없도록 모든 상태를 원래대로 되돌려야 한다.

예를 들면 하나의 컨텍스트에 Database 에 여러번 데이터를 access 해야 하는 transaction 처리 등이 있다. (DB access가 실패를 하거나 로직 에러가 났을 때 commit 을 취소하고 원복하는 트렌젝션 처리에 대한 고려는 기본이다)

물론 시도때도 없이 프로세스를 죽여야 한다는 뜻은 아니다. 프로그래머가 이러한 상황을 명확하게 정의하고 프로세스를 죽일지 단순히 에러를 리턴해야 할지 여부를 결정해야 한다.

예를 들면 DB나 Redis 같은 필수 서비스에 애초에 Connection을 맺지 못하는 경우는 Critical 한 에러로 간주하고 프로세스를 종료시키는 것이 맞다.
하지만 외부 서비스의 Open API를 호출해서 데이터를 가져오는 로직의 에러는 외부 서비스가 사용 불능한 상태가 될 가능성이 있음을 인정 하므로 서비스 불능 상태를 에러로 리턴해주는 것이 좋을 것이다.


K8S로 관리되는 시스템의 철학은 전체 시스템의 Stability, 그리고 개별 서비스의 Reliability 이다.


* Stability(안정성) 이란 시스템이 잘 안죽는 것이고, Reliability(신뢰성)이란 시스템이 (시스템이 다운 되어도)복구 가능한 것을 말한다.





에러 메세지와 로그


콘솔로그는 모니터링용이 아니다.


콘솔 로그만큼 좋은 디버깅 툴도 없다. 하지만 라이브 환경에서 콘솔로그를 너무 많이 찍는 것은 미친 짓이라는 것은 누구나 잘 알고 있다.

하지만 그 편의성 때문에 자주 사용하는 만큼 또 자주 하는 실수들 이기도 하다.

예전에 일하던 회사에서 경험담을 잠깐 공유하자면, 주니어 개발자가 남겨놓은 콘솔 로그 때문에 시스템의 I/O 자원 사용률이 크게 증가하여 서버 반응 속도에 영향을 미치는 경우를 경험한 적도 있었다. 하지만 경험 많은 개발자들이 남겨 놓은 콘솔 로그 역시 실제 라이브 서비스에서 문제가 된 경우도 적지 많다.


다른 서비스를 호출하거나 DB에 접근하는 I/O 관련된 로직의 Exception 을 콘솔로그로 남겨 놓는 경우가 많은데, 굉장히 조심해야 한다.


콘솔 로그를 (원칙적으로) 모니터링 용으로 사용하면 안되는 이유는 또 있다.

K8S 환경에서 서비스의 갯수는 가변적으로 늘어나거나 줄어들 수도 있다.
운영을 하다가 트래픽이 줄어들면 비용 절감 차원에서 서비스 수를 줄여버릴 수도 있다.

서비스가 줄어들고 컨테이너가 종료가 되면 해당 컨테이너에 남아 있던 시스템 로그는 더이상 K8S 로그 기록에 존재하지 않는다.



라이브 운영을 하는 시스템의 엔지니어가 접속해서 콘솔 로그를 보는 것은 그 시스템에 문제가 있다는 뜻이다.



네트워크


서비스가 있을수도 없을수도 있습니다

K8S는 내부 서비스 들 간의 네트워킹을 가상화 한다. K8S 클러스터 내에 서비스들의 고유 Domain 을 생성하고 내부 IP를 따로 생성하여 클러스터 내 네트워크 망을 구성한다.


A는 A.com, B는 B.com 이라는 가상 domain 을 생성한다는 뜻이다.


K8S가 서비스를 띄울 때는 순서가 없다. (물론 명시적으로 순서를 정해줄 수 있지만 그렇게 까지 복잡하게 관리를 하지 않는다. 애초에 순서대로 뜨지 않아도 되도록 각각의 어플리케이션 서비스들이 유연한 구조로 클러스터에 합류하도록 구현을 한다)

예를 들면 A라는 서비스가 B 라는 서비스에 종속적이라고 하자. (B를 호출해서 뭔가 한다는 뜻이다)


A 서비스가 실행을 하고 나면 B.com 이라는 호스트 네임으로 B에 접속을 시도한다.

여기에서 A는 connection establishment 를 실패할 시나리오는 다음과 같다.

1) 재수없게 B 서비스가 아직 한개도 뜨지 않았다.
2) 재수없게 K8S dns 가 아직 B.com 을 등록하지 않았을 수도 있다.
3) B 서비스가 아예 에러가 나서 뜨질 못했을 수도 있다.

어찌됐든 가상 네트워크 상에 resolving 을 실패할 가능성은 많이 있다. 이를 염두에 두고 프로그램을 설계해야 한다. 단순한 UDP 소켓 하나 못만드는 경우도 있다는 뜻이다.



hostname을 unique 값으로 사용하면 안된다


보통 단일 도커 환경에서 컨테이너를 띄우면 hostname 은 container ID가로 된다.

예를 들면 이런식이다.

/prometheus # cat /etc/hostname
607eb3a7c687

재차 강조하였지만, K8S orchestration 은 서비스를 언제든지 줄이고 늘릴수 있다.
컨테이너가 생성되면 무작위의 container ID가 생성되어 hostname을 갖는다.

hostname 이 라이브 서비스를 운영하는 도중에 unique 할 것이라는 생각은 머릿속에서 지워야 한다. 즉, 프로그래밍을 할때 hostname 을 받아와서 unique한 기준으로 사용해서는 안된다는 뜻이다.

모든 자원은 언제 사라져도 이상하지 않는 추상화된 자원이라는 것을 명심해야 한다.



적고 보니 네트워크 관련된 내용은 아직 깊이 있게 정리를 하진 못한것 같다.
K8S 내에서의 네트워크에 대한 이야기는 추후에 좀 더 깊게 해볼 기회가 있을 것이다.