사내 쿠버네티스 스터디

 

 

POD

POD 는 K8S에서 만들 수 있는 가장 작은 단위

POD는 보통은 컨테이너와 1 대 1로 매핑

확장을 위해서 기존 POD에 컨테이너를 추가하지 않음.(새로운 POD를 생성하거나 POD를 지움)

 

POD 하나에 여러 컨테이너를 넣을 수 있음.

(파일 관리 같은 Helper Container가 필요할 수 있음.)

-> POD만 정의하면 모든 컨테이너가 함께 작동하기 때문에 관리하기 용이.

 

이들은 서로 같은 공간을 공유하기 때문에 파일이나 통신이 용이.

 

 

 

//pod 생성
kubectl run [pod name] --image [image_name]

//pod 목록
kubectl get pods

 

 

POD들은 YAML 파일 기반으로 만들 수 있음.

# pod-def.yml

apiVersion : v1 # API 버전
kind:Pod # replicaset 등 종류
metadata: #개체에 대한 데이터
  name : myServer-pod #obeject 이름
  labels: #레이블. 이 레이블 이름을 기반으로 필터링을 할 수 있음.
    app: myServer
    type: back-end
spec: #object에 관련된 추가 정보 제공하는 곳.
  containers:
    - name: server-contaniner
    imgae: server
          
          
          
  #이를 이용하여 실행 가능        
  #kubectl create -f pod-def.yml

 

 

 

Replica Sets

pod가 다운되면, 끝남.

replication controller는 pod가 죽으면, 다시 실행시킴.

또한 node에 리소스가 부족하면, 다른 node에 pod를 실행함.

 

replica set 과의 차이점은?

replica controller가 replica set드로 대체되고 있다고 함.

 

# rc-def.yml. replica controller

apiVersion : v1 # API 버전
kind:ReplicaController
metadata: 
  name : myServer-pod #obeject 이름
  labels: #레이블. 이 레이블 이름을 기반으로 필터링을 할 수 있음.
    app: myServer
    type: back-end
spec: #object에 관련된 추가 정보 제공하는 곳.
  template: # pod 정보 작성
    # pod 정보
    metadata:
      name: myServer-pod
      labels:
        app: myServer
        type: bacek-end
    spec:
      containers:
        - name: server-contaniner
        imgae: server
        
  replicas: 3
          
          
          
  #이를 이용하여 실행 가능        
  #kubectl create -f rc-def.yml
  
  
  
# rs-def.yml. replica set
  
  
apiVersion : apps/v1 # replica controller와 다름. 이렇게 선언 안 하면 에러 발생?
kind:ReplicaSet
metadata: 
  name : myServer #obeject 이름
  labels: 
    app: myServer
    type: back-end
spec: #object에 관련된 추가 정보 제공하는 곳.
  template: # pod 정보 작성
    # pod 정보
    metadata:
      name: myServer-pod
      labels:
        app: myServer
        type: bacek-end
    spec:
      containers:
        - name: server-contaniner
        imgae: server
        
  replicas: 3
  selector: # controller와 다른 점. set은 필수로 선언 필요. contrlloer는 기본으로 정의파일에서 정의한 레이블이랑 같다고 판단
    matchLabels:
      type: back-end
    
  #이를 이용하여 실행 가능        
  #kubectl create -f rs-def.yml

 

select가 정확히 무슨 역할을 하는지?

이미 생성되어 있던 pod를 모니터링할 수 있음.

하나가 고장나면 재배포도 가능.

 

만약 이미 3개가 있는 상태에서 replicas를 3으로 설정하면?

이미 3개가 있으니까 다시 pod를 재배포하지 않음.

-> pod를 재배포하지는 않지만, spec은 적어야 함. 나중에 다운될 수도 있기 때문.

 

scale 하고 싶으면,

// replicas 설정하고
k replace -f rs-def.yml

k scale --replicas=6 -f rs-def.yml

k scale --replicas=6 replicaset myServer

 

 

Deployments

여러 pod가 있을 경우, 모두 한 번에 업데이트하는 것보다는 하나 씩 하는 것이 좋음

-> 이를 롤링 업데이트라고 함.

 

만약 하다가 문제가 생기면 롤백을 할 수 있어야 함.

보통 pod 들은 replica set을 이용.

deployment는 롤링 업데이트 등 인스턴스를 매끄럽게 업데이트 해줌.

 

deployment > replica set > pod

# df-def.yml
  
  
apiVersion : apps/v1 # replica controller와 다름. 이렇게 선언 안 하면 에러 발생?
kind: Deployment # 대문자 지켜야 함
metadata: 
  name : myServer-deployment #obeject 이름
  labels: 
    app: myServer
    type: back-end
spec:
  template:
    # pod 정보
    metadata:
      name: myServer-pod
      labels:
        app: myServer
        type: bacek-end
    spec:
      containers:
        - name: server-contaniner
        imgae: server
        
  replicas: 3
  selector:
    matchLabels:
      type: back-end
    
  #이를 이용하여 실행 가능        
  #kubectl create -f df-def.yml

 

파일은 replica set이랑 큰 차이가 없음.

 

 

 

Namespaces

dev, stg, prod 처럼 서로 공간을 분리할 수 있음.

-> 다른 name space의 리소스를 실수로 건들이지 않도록 할 수 있음.

 

dns

같은 name space안에서는 service 이름으로 서로 호출 가능.

 

다른 name space의 service를 부르려면? 

-> [service 이름].[name space].svc.cluster.local ..?

cluster.local은 domain, svc는 service 를 뜻함.

 

 

# pod-def.yml

apiVersion : v1 # API 버전
kind:Pod # replicaset 등 종류
metadata: #개체에 대한 데이터
  name : myServer-pod #obeject 이름
  namespace: dev
  labels: #레이블. 이 레이블 이름을 기반으로 필터링을 할 수 있음.
    app: myServer
    type: back-end
spec: #object에 관련된 추가 정보 제공하는 곳.
  containers:
    - name: server-contaniner
    imgae: server
          
          
          
  #이를 이용하여 실행 가능        
  #kubectl create -f pod-def.yml

namespace는 metadata아래에 선언하면 됨.

 

 

namesapce를 만들려면?

apiVersion : v1
kind:Namespace 
metadata: #개체에 대한 데이터
  name : dev

 

 

 

반응형

평소 TDD나 클래스 설계에 관심이 있었습니다.

 

그래서 책을 통해 공부는 했지만, 이 지식을 이용해 구현한 코드가 제대로 된 구조인지 궁금해서 이 프로그램에 참여했습니다.

(현재 회사에서는 코드 리뷰 문화가 없습니다 ㅎㅎ..)

 

 

 

과제가 총 4개 있고, 이를 구현하고 PR을 보내면 리뷰를 받는 방식으로 진행이 되고 있습니다.

리뷰는 현업자 분들에게 받기도 하고 해당 프로그램이 가격대가 나가는 편이라서 꼼꼼하게 리뷰 해주십니다.

 

 

 

인상깊었던 점은 캐시 관련해서 인상깊었습니다.

내용이 인상깊었다기 보다는 제 공부 태도에 대해 반성을 할 수 있었습니다.

 

자주 이용하는 클래스는 계속 새로 만들기 보다는 캐싱을 해서 이용하면 heap 공간을 절약할 수 있습니다.

Java의 Integer에 캐싱을 이용하는 것은 알고 있었습니다. 하지만 이를 응용할 생각은 전혀 못하고 있었습니다.

공부할 때 그렇구나하고 끝내는 게 아니라 어떻게 응용할 것인지까지 고민을 해봐야 겠습니다.

 

 

 

하지만 주변에 추천은 하지 않을 것 같습니다..

물론 리뷰도 잘 해주고 좋지만, 이 과정이 80만원이나 할 이유는 없다고 생각을 합니다.

돈에 여유가 있다면 괜찮지만, 저 같은 사회 초년생에게는 추천을 안할 것 같네요.

 

 

 

그래도 이 과정 덕분에 3~4월 동안 공부 열심히 했네요.

반응형

이번에는 친구가 말해줘서 알게 된 내용입니다.

 

 

 

보시면, 두 값이 128로 같은데 테스트가 실패하는 것을 알 수 있습니다.

 

 

반면 127로 했을 경우 성공합니다.

 

실제로 Integer값이 들어갈 때, 이 valueOf를 이용합니다

 

high 값은 이렇게 설정되어 있습니다. java를 실행할 때 아무 옵션을 주지 않았다면, 127로 되어 있습니다.

 

그래서 -128부터 127까지는 캐시에 있는 값을 들고 오고, 아닌 경우에는 새로운 객체를 만듭니다.

즉, -128 부터 127까지는 같은 객체를 재활용하며, 이 이상 범위는 새로운 Integer 객체를 만들어서 반환합니다.

 

그렇기 때문에, 

 

이런 식으로 테스트를 하게 되면 객체의 주소값을 비교해서 이러한 현상이 나오게 됩니다.

 

 

그래서 객체를 비교할 때는 equals()를 써야 합니다.

 

이런 내용은 처음 알았네요.

 

 

참고 자료 : https://stackoverflow.com/questions/1700081/why-is-128-128-false-but-127-127-is-true-when-comparing-integer-wrappers-in-ja

반응형

이번 주제는 테스트 관련입니다.

 

스프링에서는 @Value annotation을 통해서 application.properties에서 값을 가져와서 변수에 줄 수 있습니다.

이런 식으로 자주 이용합니다.

 

그렇다면 이를 테스트 하려면 어떻게 해야 할까요?

 

이렇게 테스트를 할 수 있습니다. 만약 @SpringBootTest가 없으면 name에 null이 들어갑니다.

 

하지만 서비스가 커지면, @SpringBootTest를 할 시에 테스트 실행 속도가 느려집니다. 그리고 다른 곳에서 bean을 주입할 때 에러가 생기는 등 이러한 현상이 발생하게 되면 이 테스트는 실패하게 됩니다.

 

 

단위 테스트를 하려면 어떻게 해야 할까요?

이런 식으로 할 수 있습니다.

 

만약 MyService에서 스프링 빈 주입 없이 단위 테스트를 하려면, setter를 선언해주거나 아니면 

Assertions.assertEquals("input", new MyService("input").getName());

 

이렇게 생성자를 따로 만들어줘야 합니다. 이는 lomok의 @RequiredArgsConstruct를 넣어도 소용 없습니다.(final로 선언되어 있지 않아서)

 

그리고 @Value를 사용하려면 final을 붙이면 안되는데, 이는 다른 곳에서 변경을 할 수도 있다는 것을 의미합니다.

그래서 위의 방법이 저는 좋다고 생각합니다.(물론 생성자를 정의해줘야 하는 단점이 있습니다.)

 

일단 @Value를 통해 값이 제대로 들어가는 지 확인해봤습니다.

테스트를 통과합니다!

 

그렇다면 @SpringBootTest 없이 테스트 진행해보겠습니다.

이렇게 할 수 있습니다!

 

단위 테스트하기 좋아진 것을 볼 수 있습니다.

반응형

이번에 회사에서 AOP에 관한 테스트 코드를 작성하다가 겪은 문제입니다.

 

로깅을 위해 AOP를 많이 이용합니다.

근데 AOP가 동작하는 지 테스트를 하려면 어떻게 해야 할까요?

 

 

이런 코드가 있고, redis에 어떤 값이 들어가거나 꺼낼 때 로깅을 하고 싶다고 가정해보겠습니다.

 

간단하게만 작성했습니다. AOP를 이용하면 이러한 코드가 나올 것 입니다.

 

근데 Redis에 관해 어떠한 설정도 없이 AOP가 작동하는 지 테스트하고 싶으면 어떻게 할까요?

 

저는 처음에 MockBean을 이용하려고 했습니다.

하지만 AOP의 부분은 동작하지 않았습니다.

 

이렇게 만든 repository가 다른 취급되는 지 궁금해서 한 번 출력해봤습니다.

이렇게 나오는데, 그러면 AOP의 Around 조건에 만족한다고 저는 생각했습니다. 하지만 AOP가 작동하지 않았죠.

 

 

 

해결책

 

일단, 해결법 부터 보겠습니다.

이렇게 하면,

작 동작하는 것을 알 수 있습니다. 왜 이러한 결과가 나왔을까요?

 

보시면, 뒤에 SpringCGLIB이라는 것이 붙은 것이 보입니다. 이는 AOP에 이용되는 proxy 객체입니다.

앞에서 봤던 거랑 다른 것을 알 수 있습니다.

 

MockBean에는 proxy 객체로 랩핑되지 않는 것을 확인할 수 있었습니다.

 

그 이유까지는 코드를 찾아봐야 하는데.... 거기까진 못하겠네요. 나중에 기회가 되면 해봐야 겠습니다.

 

만약 찾아본다면, AOP 에서 bean을 어떻게 찾아서 proxy 객체로 감싸는지 봐야될 것 같습니다. 그리고 MockBean이 언제 실행되는 지도 보면 좋을 것 같습니다.

 

 

반응형

안녕하세요. 3주 차는 건너뛰고 바로 4주 차로 왔습니다.

 

3주 차를 회고하지 않은 이유는 이전에 회고했던 내용이랑 크게 다르지 않았습니다.

 

4주 차는 생각보다 많이 어려웠습니다.

기능이 복잡하기보다는, 클래스 설계하고 테스트하기가 어려웠습니다.

 

과제 내용

간단하게 요약해서 설명드리겠습니다.

 

https://github.com/Danden1/java-christmas-6-Danden1

 

GitHub - Danden1/java-christmas-6-Danden1

Contribute to Danden1/java-christmas-6-Danden1 development by creating an account on GitHub.

github.com

자세한 문제 내용은 이를 참고해 주시면 되겠습니다.

 

12월에 메뉴를 시키면, 이를 할인해 주고 얼마만큼 할인되는지를 출력해 주는 과제입니다.

하지만 할인 전략이 매우 많습니다. 특별한 날(식당 지정)에 할인을 해주거나, 어떤 카테고리의 메뉴를 주말/평일 별로 할인을 다르게 해주는 등 전략이 매우 다양했습니다.

 

그리고 증정품을 주는 이벤트도 있는데, 증정품의 가격도 할인 가격으로 포함해서 출력을 해야 합니다.

(예를 들면, 샴페인을 증정했으면 샴페인의 가격만큼 할인되었다고 판단합니다.)

 

저는 이를 어떻게 설계할 지 고민이 많이 되었습니다.

 

클래스 설계

 

Menu 

우선 메뉴 카테고리 별로 메뉴판을 만들었습니다.

 

 

이런 식으로 구현했습니다. 

 

이렇게 하면 카테고리 별로 쉽게 관리할 수 있을 것이라고 생각했습니다.

그리고 MenuFinder는 메뉴 이름을 통해서 어떠한 메뉴의 무슨 음식인지 찾아줍니다.

 

Discounter

앞에서 설명드렸다시피, 할인 전략이 매우 다양합니다. 저는 그래서 이를 나눴습니다.

OrderRequestDto에는 메뉴 주문 시간과 메뉴 리스트들이 있습니다.

응답으로는 이렇게 할인 금액과 할인 이벤트 이름에 대한 정보를 가지고 있습니다.

 

이를 이용해서 이렇게 구현할 수 있습니다.

 

 

여기서 문제는 증정 할인이였습니다.

증정품의 가격도 할인으로 포함시키는 것이 문제였습니다.

 

 

그래서 ReseponseDto를 확장시켰습니다.

이런 식으로 했습니다. 만약 증정품이 없다면, 할인 금액이 0원인 것으로 했습니다. 이는 Optional을 활용했습니다.

 

그리고 이를 반환하도록 했습니다.

 

 

Order

주문에 관한 클래스입니다.

 

이런 식으로 구현했습니다. Discounter 목록들을 받고, 이를 활용해서 계산을 하게 됩니다.

만약 새로운 할인 전략이 추가되거나 제거되더라도 Order는 이에 관해 크게 상관이 없습니다.

이는 단위 테스트하기도 쉬워집니다.

 

 

이런 식으로 임의의 할인 전략을 만들고 이를 활용하면 됩니다.

 

이를 이용하여 Order 클래스를 테스트할 수 있습니다.

 

 

User

유저가 주문하면, 할인받은 금액에 따라서 배지를 설정해줘야 하는 기능도 있습니다.

이에 관한 클래스입니다.

 

이 부분도 앞의 Order처럼 테스트하기 쉽도록 DI를 이용했습니다.

이렇게 간단하게 할 수 있습니다.

 

즉, Order와 유저가 서로 분리되어 있습니다.

 

느낀 점

이번 과제를 하면서 많이 리팩터링을 했습니다. 테스트 코드를 구현하기 위해서 고치고 고치고 고치고... 를 반복했던 것 같습니다.

 

문제는 이렇게 하다 보니 시간이 부족했습니다. 제가 평일에는 직장을 다니다 보니, 시간이 많이 부족했습니다. 테스트 코드가 확실히 시간을 많이 잡아먹는다고 느꼈습니다. 장점이 있는 만큼 단점도 있는 법이죠.

경험이 많이 쌓이면 개발 속도는 빨라질 것이라고 믿습니다. 

(이를 위해 개발 동아리를 지원해보고 있는데, 쉽지 않네요...)

 

그리고 요구사항을 제대로 문서로 정리하지 않아서 몇몇 기능(메뉴 20개 이상 주문 불가, 10,000원 이하 주문 불가 등)이 빠졌습니다. 문서화를 꼼꼼하게 해야 될 필요성을 느꼈습니다.

귀찮다고 대충 하면 개발 단계에서 피보네요.

 

개발은 진짜 꼼꼼함이 중요한 것 같습니다.

 

 

 

전체적으로 작년보다 실력이 많이 늘어서 기분이 좋네요.

5기 때, 저랑 같이 참여하던 친구가 있습니다. 이 친구랑 5기 때도 서로 코드리뷰를 했었는데, 이번에도 진행했습니다.

실력이 많이 늘었다고 친구도 놀라워했습니다. 

 

앞으로 더 노력하는 개발자가 되어야겠네요.

반응형

작년에 참가했었지만, 2주 차는 다른 문제였습니다.

 

https://github.com/Danden1/java-racingcar-6/tree/Danden1

 

GitHub - Danden1/java-racingcar-6

Contribute to Danden1/java-racingcar-6 development by creating an account on GitHub.

github.com

 

이번에는 테스트 코드 작성을 위해 리팩토링을 많이 진행했었습니다.

 

이번 조건으로는 랜덤으로 9까지 숫자를 생성하는데, 4 이상 일 경우만 전진을 진행하도록 해야 했습니다.

이 랜덤 값을 테스트하기 위해서 리팩토링을 진행했습니다.

 

처음 코드입니다.

처음에 짠 코드가 저 빨간 줄 입니다. 이는 테스트할 방법이 없습니다.

그래서 추가한 것이 초록색 줄입니다. MoveCommandMaker를 통해 테스트하도록 했습니다.

이렇게 테스트를 했습니다.

하지만 이는 4 이상일 경우 전진하는 지 테스트를 하지 못합니다.

 

그래서 리팩터링을 진행했습니다.

리팩터링한 후의 코드입니다.

요구사항을 테스트할 수 있도록 바꿨습니다.

 

테스트 코드에 대해서도 공부를 해볼 필요가 있네요. 공부할 것이 너무 많네요.

반응형

작년에도 프리코스에 참가를 했었습니다.

 

작년과 비교하여 얼마나 발전했는지 궁금해서 이번에 참여해 봤습니다.

 

작년 코드

 

우선 작년에 구현했던 코드 구조부터 한 번 보겠습니다.

https://github.com/Danden1/java-baseball/tree/Danden1

 

GitHub - Danden1/java-baseball: 숫자 야구 게임 미션을 진행하는 저장소

숫자 야구 게임 미션을 진행하는 저장소. Contribute to Danden1/java-baseball development by creating an account on GitHub.

github.com

 

이렇게 분리했었네요.

 

Model에 숫자를 만들고, 유효성을 검사하고 strike가 몇 개인지 판단하는 등 모든 기능이 들어가 있습니다. 이는 확장성에 별로죠.(SRP 위반) 지금 보니까 진짜 대단하네요....

 

이후에 인턴을 하고 공부를 하면서 엄청나게 발전한 것이 느껴집니다.

 

 

 

어떤 점이 발전했을까요?

 

현재 코드

 

그럼 이번에 작성한 코드 구조입니다.

https://github.com/Danden1/java-baseball-6/tree/Danden1

 

GitHub - Danden1/java-baseball-6: 2번 째 참여

2번 째 참여. Contribute to Danden1/java-baseball-6 development by creating an account on GitHub.

github.com

이런 식으로 설계를 했습니다.

 

domain layer

그리고 domain layer부터 한 번 보겠습니다.

저는 도메인을 이렇게 분석했습니다. 숫자야구 결과를 반환해 주는 심판이 있고, 숫자를 생성해 주는 인터페이스가 있습니다. 그리고 숫자 야구 결과에 대한 클래스도 있습니다. 

 

그리고 BaseballNumber를 만들기 위해 의존성 주입을 이용했습니다.

이렇게 함으로써 유연한 코드를 작성할 수 있습니다.

아래는 이를 이용하여 작성한 테스트 코드입니다.

게임 결과를 테스트하는 코드입니다. 의존성 주입을 사용함으로써 단위 테스트가 가능하게 됩니다. 어떻게 숫자를 만드는지는 크게 관심이 없습니다. 그래서 임의로 123을 반환하는 클래스를 만듦으로써 테스트를 쉽게 할 수 있습니다.

 

infra layer

 

infra layer에는 BaseballNumberGenerator 인터페이스를 구현한 구현체가 있습니다.

왜 이를 infra라고 생각을 했냐면, 컴퓨터의 숫자는 랜덤으로 생성할 수도 있고 특정한 패턴을 가지고 생성할 수도 있다고 생각하여 확장성을 고려해서 설계했습니다. 이 부분은 기술적인 부분이라고 생각합니다. 

Type은 Enum으로 USER, COMPUTER 가 있습니다. 나중에 factory를 이용하여 타입에 맞는 해당 객체를 반환하게 됩니다.

이렇게 해당 타입에 맞는 BaseballNumberGenerator를 반환하게 됩니다. 나중에 난이도에 따라서 computer가 생성하는 숫자가 달라진다면, 여기다가 타입(난이도)만 더 추가해 주면 됩니다. 이는 확장을 하기 쉬워집니다.

그리고 싱글톤을 이용함으로써 객체가 생성되는 데에 드는 오버헤드가 줄어듭니다.

 

Computer에 해당하는 테스트 코드입니다.

아쉬운 점은 Baseball의 BASBALL_NUMBER_LEN에 의존적인 부분입니다. 만약 baseball의 len을 변경하게 되면 이는 실패하게 됩니다. 왜냐하면 baseball.size()가 3이라고 이미 가정을 했기 때문입니다. 이 부분이 많이 아쉽네요.

 

사실 BaseballNumberGenerator의 기능만 테스트한다기보다는 실제로 숫자야구가 만들어지는 동작을 테스트하는 경우입니다. 

좋은 코드, 나쁜 코드 책에서는 실제 기능(함수)만 테스트하기보다는 동작을 테스트하라고 합니다. 이는 여러 가지 함수를 거쳐서 실행될 수 있다고 합니다. 이를 고려하면 괜찮다는 생각이 들기도 하네요.

appl layer

이렇게 factory를 이용하여 baseballNumberGenerator를 받아서 숫자를 생성하게 됩니다.

 

presentation layer

service를 이용하여 게임을 실행하게 됩니다.

 

후기

1년 사이에 엄청나게 발전한 것 같습니다. 하지만 아직 부족한 점이 많이 느껴지네요. 너무 기능을 쪼개고 확장성에 초점을 둔 것 같기도 하다는 생각이 듭니다. 그리고 DDD를 이용하고 싶은데 실제로 이렇게 이용하는 것이 맞는지 궁금하네요.

 

DDD에 대해서 공부할 필요성이 있다고 느껴서, 도메인 주도 설계 첫걸음 이라는 책을 사서 현재 공부해보고 있습니다.

 

 

 

다른 분들에게 리뷰를 받아보고 싶어서 글을 올렸었는데, 슬프게도 리뷰해 주시는 분이 안 계시네요.

 

제가 먼저 접근을 해봐야겠습니다.

 

 

좀 더 노력을 해서 1인분 넘게하는 개발자가 되고 싶네요.

반응형

 

이번에는 라인 유튜브에 올라와있는 것을 보고 정리한 글입니다.

 

틀린 내용이 있을 수도 있습니다. 틀린 내용이 있다면, 알려주시면 감사하겠습니다.

 

 

 

문제 상황

 

kafka broker를 rolling restart 했을 때,

message produce가 실패하는 현상 발생 했음.

-> produce reqeust timeout이 발생했다고 함.

 

restart 했던 broker의 response time은 정상이였지만, producer의 request latency가 거의 16초나 되었다고 함.

 

 

왜 이런 현상이 나타났을까?

그렇다면 request는 어떠한 과정으로 일어나는 지?

 

producer -> broker -> producer 이런 식으로 latency를 측정함.

broker는 데이터를 완전히 받는 직후부터 시간을 측정함.

 

 

broker를 restart 했을 때, node_netstat_TcpExt_SyncookiesSent가 spike였음(값이 높았음)

 

TCP SYN Cookies에 대해 알아야함.

이는 syn flloding을 막기 위해 이용하는 것임.

 

SYN flood attack 이란?

 

TCP 통신할 때, syn 만 보내고, SYN + ACK 을 무시함. 그러면 서버는 syn queue에 이 SYN을 보관하다가 다 차게 됨.

그러면 syn cookie를 이용하게 됨. 

 

syn cookie는 SYN 패킷에 있는 정보들을 이는 SYN + ACK의 ISN(Initial Sequence Number)로 만들어서 클라이언트에게 보냄.(원래는 random 값) 클라이언트가 서버에게 ACK을 보내면, 이 ACK의 정보를 decoding해서 connection을 맺게 됨.

 

 

 

 

그럼 왜 SYN flood가 일어났는지?

 

broker를 restart하면, 이 broker에 접속했던 client들이 일단 다른 broker에 접근을 하게 됨. restar가 되면, client는 broker들에게 fail-over를 하게 됨. 그리고 본래 broker들에게 기존 client들이 접속하게 됨.

연결하려는 client가 많아져서 syn flood 가 일어남.

 

 

tcp window가 문제가 됨?

요약하면, tcp window는 receiver가 처리할 수 있는 만큼의 양만 달라고 하는 것.

만약 데이터가 들어오는 속도가 처리 속도보다 빠르다면 데이터가 유실될 수 있음.

 

tcp 옵션에 window scaling 라는 옵션이 있음. 이는 windowsize * (2^scaling) 값이 최종 window size가 됨.

이를 이용하는 이유는 기존 window size의 최대값이 16비트 밖에 되지 않아서 그럼.

하지만 SYN cookie의   seq number는 32bit임. window scaling factor를 이용하지 못하게 됨.

-> 이는 한 번에 보낼 수 있는 데이터의 양이 적어지고 이는 throughput을 감소시킴.

 

근데 linux는 tcp timestamp가 유효하면, 이를 이용해서 window scaling 을 내장하는 기능이 있음.

그리고 scaling factor가 없어진다고 해도, timeout이 일어나는지?

 

 

실제로 net.ipv4.tcp_syncookies 값을 2로 설정하고 테스트해봤다고 함.

-> 모든 connection을 syncookies를 이용하여 연결함.

 

window scaling이 유효해야 하는 상태에서 지연이 일어나고 있었음?(잘 모르겠음)

-> broker 의 response time은 정상이지만, producer는 그렇지 않았음.

 

producer는 ack을 계속 기다리고 있었음. window size(789)가 작아서 이러한 현상이 일어났다고 함

 

window scaling 값도 1이였음. 즉 실제 window size는 789 * 2 ^ 1 = 1578이였음. 

 

 

그럼 왜 이렇게 window size가 작았을까?

커널을 분석해봤다고 함. 근데 서로 window size가 달랐음.

출처 : https://www.youtube.com/watch?v=_2F_qdwfUas&ab_channel=LINEDevelopers

broker의 window scaling factor가 7로 나옴(아까는 1로 나왔었음)

즉, producer는 1로 인지하고 있고, broker는 7로 알고 있음.(64배 차이남)

그래서 계속 기다리고 있었음.

 

실제 적용되는 값을 찾기 위해,

리눅스 커널의 tcp_select_window 함수로 window scaling 값을 조절함. 그래서 이를 hook 하는 방법을 생각함?

BPF를 이용하면 이 부분이 가능하다고 함. 유저가 작성한 프로그램이 커널에서 작동할 수 있다고 함.

 

실제 broker의 window scaling 값은 7이였음. 7을 이용하여 window size를 계산했음.

 

broker는 window scaling foactor를 7로 알고 있는 상태에서 데이터를 보냄., producer는 1로 알고 있는 상태임.

producer는 window size가 작았음. producer는 데이터를 보낼 때 마다 ack을 기다리고 있었음. 그렇기 때문에 producer reqeust 시간이 오래 걸리게 되고, 이는 timeout을 발생시킴.

 

왜 브로커 > producer 의 값과 실제 connection에 이용되는 값이 달랐을까?

-> 커널의 소스 코드에 문제가 있었다고 함.(linux kernel 3.10) 5.10 버전에 이 버그 해결되었다고 함.

 

 

해결책

 

이를 해결하기 위해, syn flood가 일어나지 않았다고 판단하도록 하게 했음.

즉, syn cookie를 끄는 방법.

-> 이는 syn flood가 일어나는 동안 syn 을 drop 하게 됨. 정상적인 접속이기 때문에 syn retry를 하게 됨. 이는 지연이 발생할 수 있음.

 

kafka의 listen backlog size를 늘리면 된다고 함. 하지만 이는 50으로 하드코딩 되어 있어서 늘릴 수 없다고 함.

이 부분을 논의 중임.

 

 

느낀 점

정말 대단하다고 느꼈습니다. 백엔드 개발자의 cs 지식이 정말 중요하다는 것을 느끼게 되었습니다.

 

궁금한 점이 하나 있습니다.

처음에는 단순히 window size가 작아서 발생한 문제라고 처음에는 이해했었습니다.

근데 broker window scaling factor가 1이면, window size의 문제는 아닌 것 같네요.

 

그렇다면 syn flood가 일어났고, 이 때문에 syn cookie를 이용해서 통신을 하게 되는데 커널 버그 때문에 서로 window size가 다르게 알고 있어서 이런 이슈가 발생한 것으로 이해했습니다.

 

그렇다면, 이 버그가 해결된 5.10 버전의 커널을 사용할 때는 syn cookie(syn flood가 일어나도)를 이용해도 이러한 문제점이 발생하지 않는 걸까요?

 

 

 

window size가 다르게 되면 어떠한 현상이 일어나는 지 알아볼 필요성이 있는 것 같습니다.

 

 

출처 : https://www.youtube.com/watch?v=_2F_qdwfUas&ab_channel=LINEDevelopers 

https://en.wikipedia.org/wiki/SYN_cookies

반응형

'BackEnd > Kafka' 카테고리의 다른 글

[Kafka] partition, consumer, producer 관계 (2)  (0) 2023.02.08
[Kafka] partition, consumer, producer 관계 (1)  (0) 2023.02.08

이번에 일을 하면서 개발 환경 구축을 하게 되었습니다.

 

보통 대부분의 프로젝트가 db를 이용할 것 입니다.

하지만 로컬에서 테스트를 해보기 위해 stage나 live 서버의 데이터를 조작하면 그 db로 테스트하고 있던 사람은 불편하겠죠.

 

그렇다고 새로운 프로젝트에 인원이 투입될 때마다, 로컬에 맞는 db를 만들면 시간이 매우 낭비됩니다.

 

이를 해결하기 위해서 DB 세팅(table, user, function, index 등)을 db에 올리고 테스트할 수 있도록 합니다.

 

보통 docker-compose를 이용하여 합니다.

version: '3.1'

services:
	db:
    	images:mysql
        ...
        ...
        volumes: ./test /docker-entrypoint-initdb.d/
        ...

하지만 db 관련 정보는 volume을 이용하기 때문에 이 컨테이너를 commit 해도 db 세팅했던 것들은 유지되지 않습니다.

 

물론 로컬에 마운트를 하면 백업 본이 생기지만, 다른 곳에서는 이미지를 가져와서 실행하면 백업 본도 못 봅니다.

 

 

이를 해결하기 위해서 직접 Dockerfile을 수정했습니다.

이는 github에서 손쉽게 구할 수 있습니다.

 

Dockerfile을 보면,

volume이 설정되어 있는데, 이를 지워주면 됩니다. 

그리고 아래를 보면 COPY ... 이 있는데 이 파일도 가져와야 합니다(git에 Dockerfile이랑 같이 있습니다).

 

ENV POSTGRES_USER <user name>

ENV POSTGRES_PASSWORD <password>

COPY <sql path> /docker-entrypoint-initdb.d/

그리고 이렇게 더 넣어줘야 합니다.

만약 db 세팅이 필요가 없다면 COPY 부분은 안 넣어도 됩니다.

 

아니면, /bin/bash에 들어가서 직접 table을 만드는 등 세팅을 해주시고

docker commit을 하면 됩니다.

 

 

이렇게 하면 이미지를 배포할 경우 다른 사용자들이 db를 그대로 활용할 수 있습니다.

데이터(row)도 유지할 수 있습니다.

 

 

 

이상하게도 저는 

이 부분에서 계속 general error가 발생했다고 합니다. 

 

링크를

keyserver.ubuntu.com

로 수정하니까 잘 되었습니다.

 

저도 그 이유는 잘 모르겠네요. 여기서 삽질을 엄청한 것 같습니다.

 

반응형

+ Recent posts