포스트

2023-09-04-TIL

Today I Learned

MD5 Hashing in Java

Java에서 문자열이나 파일의 MD5 해시를 만드는 방법을 찾아봤다. MD5는 128비트 해시를 만드는 해시 함수이고, 표준 라이브러리만 쓴다면 java.security.MessageDigest를 MessageDigest.getInstance("MD5")로 만든 뒤 update()로 입력을 넣고 digest()로 결과 바이트를 얻는다. 파일처럼 긴 입력은 update()를 여러 번 호출하며 나눠 넣으면 된다. Baeldung 글은 MessageDigest가 스레드 안전하지 않으므로 스레드마다 새 인스턴스를 쓰라고 강조한다.

1
2
3
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(input.getBytes(StandardCharsets.UTF_8));
byte[] digest = md.digest();

16진수 문자열이 바로 필요하면 Apache Commons Codec의 DigestUtils.md5Hex()가 더 간단하다. 다만 MD5는 보안 용도로 쓰기 어렵다. RFC 6151은 MD5가 충돌 저항성이 필요한 곳, 예를 들어 디지털 서명에는 더 이상 받아들일 수 없다고 정리한다. 파일 무결성 확인용 체크섬이나 캐시 키처럼 충돌 공격을 고려하지 않아도 되는 곳에 쓰는 것이 맞다.

printStackTrace() vs log.error(“error”, e)

예외를 출력하는 방법별 차이를 찾아봤다. e.getMessage()는 원인 메시지만, e.toString()은 예외 타입과 메시지를, e.printStackTrace()는 예외가 발생한 지점까지의 호출 스택 전체를 출력한다. Javadoc에 따르면 printStackTrace()는 System.err, 즉 표준 에러 스트림에 쓴다. 그래서 로깅 프레임워크의 레벨, 포맷, 파일 출력 설정을 거치지 않는다.

SLF4J에서는 예외를 마지막 인자로 넘기면 스택 트레이스까지 로그에 남는다. SLF4J FAQ는 마지막 파라미터가 예외면 메시지의 남는 인자가 아니라 예외로 해석한다고 설명하며, 이 동작은 1.6.0부터다. 예외를 문자열에 이어 붙이면 toString() 결과만 남고 스택 트레이스는 빠지므로, log.error("error", e)처럼 예외 객체 자체를 넘기는 것이 맞다.

1
log.error("Failed to format {}", s, e); // e의 스택 트레이스까지 기록된다

Today I Studied

Kubernetes

쿠버네티스 공식 문서는 쿠버네티스를 컨테이너화된 워크로드와 서비스를 관리하기 위한 이식 가능하고 확장 가능한 오픈소스 플랫폼으로, 선언적 구성과 자동화를 모두 지원한다고 정의한다. 제공하는 기능으로는 서비스 디스커버리와 로드 밸런싱, 스토리지 오케스트레이션, 자동화된 롤아웃과 롤백, 자동화된 빈 패킹, 자가 치유, 시크릿과 구성 관리, 배치 실행, 수평 확장 등을 든다. 반대로 소스 코드 빌드와 배포, 미들웨어나 DB 같은 애플리케이션 레벨 서비스, 특정 로깅이나 모니터링 솔루션은 제공하거나 강제하지 않는다. 함께 찾아본 국내 글들은 대부분 이 목록을 “왜 쿠버네티스를 쓰는가”의 근거로 풀어 쓴 것이다.

Kubernetes vs AWS Auto-Scaling

아래 토론의 “AWS에 auto-scale 기능이 있는데 k8s를 직접 쓰는 장점이 있나”라는 질문 때문에 찾아봤다. AWS Auto Scaling Group은 CPU 사용률이나 네트워크 트래픽 같은 지표에 따라 가상 머신 인스턴스 수를 조절하고, 쿠버네티스의 HPA와 Cluster Autoscaler는 컨테이너 수준에서 파드 수와 노드 수를 조절한다. CloudThat 글은 Auto Scaling Group에는 서비스 관리 기능이 없다는 점을 가장 큰 차이로 든다. 서비스 디스커버리, 로드 밸런싱, 롤링 업데이트까지 필요하면 쿠버네티스, VM 단위 확장만 필요하면 ASG가 맞다는 결론이다.

Split-Brain

스플릿 브레인은 클러스터 구성원들이 서로 통신하지 못하지만 각자는 정상 동작하는 상태에서, 여러 노드가 스스로를 Primary로 인식하고 공통 자원의 소유권을 동시에 가져가는 상황이다. 데이터가 양쪽에서 따로 쓰이면서 동기화와 복제가 깨진다. 해결책은 쿼럼(정족수)이다. 과반의 표를 얻은 쪽만 계속 동작하고, 정족수를 채우지 못한 쪽은 멈추게 해서 두 개의 Primary가 동시에 존재하지 않도록 한다.

ETCD

etcd consensus algorithm high availability

etcd는 Raft 합의 알고리즘으로 여러 멤버가 같은 데이터를 갖도록 유지하는 분산 키-값 저장소다. Raft는 CP 성향의 알고리즘인데 어떻게 고가용성을 말할 수 있는지가 Stack Overflow 질문의 요지였다. etcd FAQ의 표를 보면 답이 나온다. 쓰기가 진행되려면 과반((n/2)+1)의 멤버가 살아 있어야 하므로, 멤버가 3개면 1개, 5개면 2개, 7개면 3개의 장애까지 견딘다. 짝수로 늘려도 견딜 수 있는 장애 수는 같고 정족수만 커지므로 홀수 구성을 권하고, 5개면 대부분의 경우 충분하며 쓰기 성능 때문에 7개를 넘기지 않는 것이 좋다고 한다. 즉 과반이 살아 있는 동안에는 가용성을, 과반을 잃으면 일관성을 택하는 구조다.

ETCD and Kubernetes

etcd 문서의 “etcd versus other key-value stores”는 etcd를 대규모 분산 시스템의 메타데이터를 일관되고 장애에 강하게 저장하는 기반으로 설명하며, 설정 관리, 서비스 디스커버리, 분산 작업 조율처럼 가용성보다 일관성이 중요한 용도를 겨냥한다고 말한다. 쿠버네티스 API 서버는 클러스터 상태를 etcd에 저장하고, etcd의 watch API로 클러스터를 지켜보며 설정 변경을 반영한다. 아래 토론에서 나온 “마스터 노드를 3개 띄울 때 각 마스터가 ETCD 1, 2, 3을 모두 알아야 하는 이유”도 여기서 나온다. 각 etcd 멤버는 투표와 복제에 참여하기 위해 나머지 멤버의 주소를 알아야 한다.

EKS vs Kubernetes

EKS를 쓰면 AWS가 etcd와 API 서버를 포함한 컨트롤 플레인을 여러 가용 영역에 걸쳐 운영하고 패치와 업그레이드를 맡는다. 사용자는 데이터 플레인만 관리하며, 직접 관리하는 노드, 관리형 노드 그룹, Fargate 중에서 고를 수 있다. 직접 구축한 쿠버네티스는 컨트롤 플레인과 데이터 플레인을 모두 직접 운영해야 하는 대신 통제권이 크다. TechTarget 글은 EKS의 단점으로 컨트롤 플레인 비용과 느린 버전 반영을 든다.

AWS ECS vs Kubernetes

NetApp 글은 ECS와 쿠버네티스를 비교하는 것이 공정하지 않다고 말한다. ECS는 컨테이너 오케스트레이션 플랫폼과 그것을 운영하는 관리형 서비스를 한 제품에 묶은 것이지만, 쿠버네티스는 오케스트레이션만 제공하기 때문이다. 그래서 실질적인 비교 대상은 ECS와 EKS다. 글의 결론은 작은 배포에는 설정이 간단한 ECS, 큰 규모나 하이브리드 환경에는 이식성이 좋은 EKS가 맞다는 것이다.

Imperative vs Declarative

명령형 프로그래밍은 “어떻게(How)” 할지를 순서대로 기술하고, 선언형 프로그래밍은 “무엇을(What)” 원하는지를 기술한다. SQL이 대표적인 선언형 예다. 원하는 데이터의 조건만 적고 가져오는 방법은 DB가 정한다. 쿠버네티스의 매니페스트도 같은 방식이다. 원하는 상태를 선언해 두면 컨트롤러가 실제 상태를 그 상태로 맞춘다. 공식 문서가 쿠버네티스를 “선언적 구성”을 지원하는 플랫폼이라고 소개하는 이유가 이것이다.

Kubernetes Scheduler

kube-scheduler는 컨트롤 플레인에서 실행되며, 노드가 정해지지 않은 새 파드를 감시하다가 각 파드가 실행될 최적의 노드를 고른다. 선택은 두 단계다. 필터링 단계에서 파드의 리소스 요청을 감당할 수 있는 등 조건을 만족하는 노드(feasible node)를 추리고, 스코어링 단계에서 남은 노드에 점수를 매겨 가장 높은 노드를 고른다. 점수가 같으면 무작위로 고르고, 조건을 만족하는 노드가 없으면 파드는 스케줄되지 않은 채 남는다.

Kubelet

kubelet은 각 노드에서 실행되는 노드 에이전트다. 노드를 API 서버에 등록하고, API 서버 등에서 받은 PodSpec에 기술된 컨테이너가 실행 중이며 건강한 상태인지 보장한다. 쿠버네티스가 만들지 않은 컨테이너는 관리하지 않는다.

Rolling Update

롤링 업데이트는 파드 인스턴스를 점진적으로 새 버전으로 교체해서 디플로이먼트를 서비스 중단 없이 업데이트하는 방식이다. 공식 튜토리얼에 따르면 기본값으로 업데이트 중 사용할 수 없는 파드의 최대 개수와 새로 생성할 수 있는 파드의 최대 개수가 각각 1개이고, 이 값은 maxUnavailable, maxSurge로 개수나 백분율로 바꿀 수 있다. 서비스는 업데이트 중 가용한 파드에만 트래픽을 보내고, 모든 업데이트는 버전으로 관리되어 이전 버전으로 롤백할 수 있다. 함께 찾아본 velog 글은 롤링 업데이트와 Blue/Green, Canary 배포 전략을 비교한다.

nGrinder

nGrinder는 네이버가 공개한 부하 테스트 플랫폼으로, 스크립트 작성, 테스트 실행, 모니터링, 결과 리포트를 한곳에서 처리한다. 테스트 스크립트를 만들고 실행을 설정하는 웹 애플리케이션인 controller와, 실제로 부하를 만드는 가상 사용자 생성기인 agent로 구성된다. 시나리오는 Jython이나 Groovy 스크립트로 작성한다. 아래 토론에서 성능 테스트 도구로 nGrinder와 k6가 언급되어 찾아봤다.

Event Bubbling

이벤트 버블링은 한 요소에서 이벤트가 발생하면 그 요소의 핸들러가 동작한 뒤 부모 요소의 핸들러가 차례로 동작하며 최상위까지 전파되는 현상이다. 캡처링은 반대로 최상위에서 대상 요소로 내려가며, addEventListener의 옵션에 { capture: true }를 주면 캡처링 단계에서 핸들러가 실행된다.

Image Optimization

테코블 글은 웹 페이지 용량에서 이미지가 가장 큰 비중을 차지하므로, 이미지 크기만 줄여도 로딩 속도가 개선된다고 설명한다. 방법은 렌더링되는 크기에 맞게 원본을 축소하는 것과, srcset과 sizes 속성으로 화면 크기에 맞는 이미지를 골라 받게 하는 반응형 이미지다. 글의 예시에서는 60px 원에 들어갈 389KB 이미지를 크기에 맞게 줄여 12.8KB로 만들었다.


스터디 토론 기록

마스터 노드를 3개 띄우는데, 일일이 적어주는 이유는? master1도 ETCD 1,2,3를 다 알아야 하고 master2도 ETCD 1,2,3를 다 알아야 하는 이유?

master가 두 개 이상이 되어야 하는 이유? -> HA 구성

이 질문에 대해 받아 둔 답변은 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ETCD is a distributed key-value store that is used to store the cluster state of Kubernetes. The cluster state includes information such as the names and IP addresses of the master nodes, the pods that are running in the cluster, and the services that are available in the cluster.

In order to maintain a consistent view of the cluster state, all of the ETCD nodes in the cluster need to be aware of each other. This is because ETCD uses a consensus algorithm to ensure that all of the nodes have the same data. If one node does not know about the other nodes, it will not be able to participate in the consensus algorithm and the cluster state will become inconsistent.

In a Kubernetes cluster, the ETCD nodes are typically co-located on the master nodes. However, it is also possible to have the ETCD nodes on separate machines. In this case, the ETCD nodes need to be configured with the IP addresses of each other so that they can communicate and maintain a consistent view of the cluster state.

To answer your question more specifically, ETCD needs to know the other ETCD in other master nodes in order to:

Maintain a consistent view of the cluster state.
Detect and resolve conflicts in the cluster state.
Recover from failures in the cluster.
Without this knowledge, ETCD would not be able to function properly and the Kubernetes cluster would not be able to operate.

I hope this answers your question. Let me know if you have any other questions.

답변의 요지는 맞지만, 멤버를 서로 알아야 하는 더 직접적인 이유는 위 etcd FAQ의 정족수다. 쓰기는 과반의 멤버가 동의해야 커밋되므로 각 멤버가 전체 멤버 목록을 알아야 투표와 복제를 할 수 있고, 마스터(와 etcd 멤버)를 홀수로 3개 이상 두어야 하나가 죽어도 과반이 유지된다.

K8s 목적?

auto-scale vs k8s?

  • dong: 리소스를 추상화해서 노드로 관리하여 pod를 활성화하는 게 목적

  • pyro: ami로 이미지로 만들어서 띄워서 컨테이너화 시키면 aws에서 auto-scale이 훨씬 용이

  • Woody: pod가 auto-scaling이 되어야 하는데 node, pods가 모두 auto-scaling이 되어야 하는데, 시점을 파악하기가 어려움. k8s의 공식적인 툴은 없고 핸들링해야 하는 부분이 많다. Pod, node 모두 보고 auto-scaling을 해야 해서 side-effect를 우려해서 안 씀

  • 저도 궁금했는데, AWS에 auto-scale 기능이 제공되는데 k8s를 직접 사용해서 느껴지는 장점이 있나요? ami로 이미지로 만들어서 띄울 수도 있고, 컨테이너화 시켜서 scale-out 할 수도 있습니다.

  • 오히려 큰 규모의 서비스는 파이로 말대로 하면 될 것 같아요

    저희는 참고로 제약사항이 좀 있어서 오토스케일 기능을 거의 안 사용합니다

  • Roach: aws 가져다 쓰는 것보다는 ..

  • dong: backend app 여러 개인 msa 구조일 때, 모듈이 많아질 때 k8s가 필요해진다.

  • woody: 또 k8s 장점이 self-healing이 가능 probe가 죽으면 다시 destroy하고 다시 살리는 게 부드러워서 도움이 많이 된다.

  • Jane: application의 단위?

  • Woody: pods가 많다 보니 관리하기가 힘들어서 k8s가 좋다. 하나의 .. 안에 여러 개의 pods가 뜨니깐 관리가 용이하다

  • dong: pod 하나 띄우는 게 프로세스 하나 띄우는 것

  • Jane: k8s 안에 물리장비가 뜨는 경우도 많은데, 한 번에 배포할 수 있도록 해놔서 k8s 관리군이랑 물리군이랑 차이를 모르고 쓰고 있었다. 물리장비(pm) 관리는 low-level의 인프라 모니터링 말고는 직접 다루고 있다.

  • woody: 모니터링은 lens 사용 중

  • dong: 물리적인 스펙과 상관없이 추상화되어서 띄우는 게 장점

메모리 사용률과 리소스 설정

  • woody: memory peak?
  • jane: 5% ~ 15%? k8s 가상장비는 80%, 알림 오면 임계치 조정하기
  • roach: 저희도 보통 하드한 거 하는 인스턴스 아니면 보통 10% 이하에요 CPU 메모리는 보통 60%로 두는 거 같아요 근데 어차피 VM 옵션으로 다 제약 걸어두니
  • dong: 근데 쿠버네티스가 메모리 80% 점유 중이면 가끔 메모리 부족해서 배포 실패 나고 그러지 않나요 우디?
  • woody: request, node는 크로스체크해야 함. request를 말하는 건지 실제 사용량을 말하는 건지 모르겠음
  • roach: 근데 성능테스트 안 하면 알 수 없지 않아요? 애초에 메모리 얼마나 둘지?
  • woody: 처음에는 충분히 크게 잡아놓고, 점차 줄여나가는 방식으로 구성

성능 테스트와 기동 부하

  • 성능테스트 도구는? ngrinder, k6
  • 성능테스트는 항상 하는가?
  • 사용자층이 운영자가 아니라 public인 경우에는 꼼꼼히 하는 편
  • WebClient vs Feign -> Feign을 여전히 많이 사용
  • coroutine으로 비동기로 wrapper layer를 하나 두어서 사용

  • GraalVM 사용? 부팅 시간을 줄이는 이유?
  • 부팅 시 드는 부하를 줄인다.
  • roach: lazy loading
  • roach: redis나 kafka는 lazy loading 하기도 함. but 비추
  • woody: 메모리까지 계속 잡아먹다가 restart되는 현상이 있는데, 원인이 너무 다양할 수 있어서 정확히는 모르지만 최초 pod가 뜰 때의 부하인 것은 확실한데 어떻게 개선할 수 있을지 고민. tps가 많지는 않은데…
  • Roach: 그래도 스프링 때문에 그 정도의 부하가 있다면 이상한 듯? runtime과 동일해야 정상인데
  • woody: 처음 트래픽을 받을 때에도 동일
  • 롤링 업데이트
  • woody: OOM이 발생하면서 재시작되기도 하고, 그냥 재시작되기도 함. probe http requests + 일반 requests -> 대기큐에 너무 쌓여서 probe가 안 되어서
  • dong: periodSeconds?
  • woody: 그 설정은 되어 있는데… 로그를 못 봐서 좀 어려울 듯
  • roach: 보통 배치서버에 graal을 사용하지 api 서버에서는 graal을 잘 사용하지 않는 것 같다?

롤링 업데이트와 수동 전환

  • jane: wait를 수동으로 부하를 이동시키고 있는데, rolling update가 이를 해소할 수 있는 기술인가?
  • woody: 기존 pod 2대가 죽고 새 pod 2대가 살아도 90분이라고 하면 그동안 기다렸다가 해야 하는 문제가 있다. 그냥 수동으로 해도 나쁘지 않을 듯?

파드 로그 확인

  • kyu: 로그 관련해서 애플리케이션 로그를 확인할 때 k8s로 띄우면 어디에 있는 로그인지 확인을 어떻게 하나? 직접 접속해서 하는가?
  • woody: app 로그를 보고 에러가 난 것 같다고 하면 pod 이름이 찍혀있는데, pod 이름이 로그에 찍혀있기 때문에 pod 이름을 보고 shell을 확인하기도 하고..
  • Kyu: 예를 들어, 로그인 에러가 발생하면 account error 같은 내용이 하나의 컨테이너에서 찍혔을 텐데

  • memory: 60% 정도 주고
참고한 자료외부 출처 55

외부 출처

이 글은 저작권자의 CC BY 4.0 라이선스를 따릅니다.

댓글

아직 댓글이 없습니다