K8S LOL.JOA 프로젝트 후기

Jiny's 개발 일기·2022년 5월 1일
0

Projects

목록 보기
1/8

프로젝트 개요

목표

간단한 웹서비스를 EKS에 배포 후 실제 K8S가 가지는 강점을 실험해 보는 것

팀원 및 역할

  • 김정한: 인프라 구축
  • 남정진: 인프라 구축, 서버 개발, 프론트 API 개발
  • 이노진: UI 구축, 반응형 구축
  • 최연제: 테스트

프로젝트 환경

  • AWS: Credit 1400

기대 효과

  1. k8s의 Auto-Scaling으로 순간적이고 폭발적인 트래픽에 잘 견딜 것이다.
  2. MSA의 도입으로 개발 및 운영 생산성이 늘어날 것이다.
  3. k8s로 효과적인 리소스 사용이 가능할 것이다.

프로젝트 내용

Main Issue

Infra

│ Error: error waiting for EKS Node Group (loljoa2-eks-cluster:loljoa2-eks-node) to create: 
unexpected state 'CREATE_FAILED', wanted target 'ACTIVE'. last error: 2 errors occurred: │ * 
eks-b2c004f2-a3a4-5681-99b9-4f6dfce8ea3c: AsgInstanceLaunchFailures: You've reached your 
quota for maximum Fleet Requests for this account. Launching EC2 instance failed. │ * 
DUMMY_284e67fe-8e60-4d87-ad4a-a7a07a889c78, DUMMY_5dd1d260-40d9-4f41-
b1cc-3b7f73ef626b, DUMMY_886765c6-0893-4f66-9f7f-789ccb815de5, 
DUMMY_8ed860a0-86ef-47da-8303-5c1d3b8a6667, DUMMY_903b3c1ec2c0-4179-8dfa-25d669e5da93, DUMMY_b1d5ea72-a084-4c56-b0dc-f38a40e1921b, 
DUMMY_b6d9d18e-daf0-4b07-8857-525282c6569a, DUMMY_c2694c63-8c59-48e4-8465-
cc7d83ea6fc6, DUMMY_ca9ead15-3d72-4640-afdd-1d606fbacefa, DUMMY_e0aed6f8-
c0d8-4276-820d-f2f34910cfa6: NodeCreationFailure: Instances failed to join the kubernetes cluste
  • 키가 해커들에게 노출되어 비트코인 채굴에 쓰임
  • AWS에서 확인하여 해당 EC2 자원을 중지 함과 서비스를 중지 시킴
  • 이메일로 통보 후 안내에 따라 조치
  • 조치 끝낸 후 서비스 재가동 요청

{"level":"error","ts":1649780740.8978348,"logger":"controllerruntime.manager.controller.ingress","msg":"Reconciler 
error","name":"ingress-2000","namespace":"game-2000","error":"WebIdentityErr: failed to retrieve 
credentials\ncaused by: AccessDenied: Not authorized to perform 
sts:AssumeRoleWithWebIdentity\n\tstatus code: 403, request id: 4e57cee3-4945-4186-85cdd3ca033258f0"}
  • 권한 문제로 로드밸런서에 연결이 안되는 이슈
  • 단순히 태그만 해주는 것이 아닌 oicd 자격 증명 공급자 생성 후 정책 만들어 역할에 부여
    • 태그 시 퍼블릿, 프라이빗 서브넷에 따른 네이밍
      • 퍼블릭: kubernetes.io/role/elb = 1
      • 프라이빗: kubernetes.io/role/internal-elb = 1

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
            "Federated": "arn:aws:iam::04------9347:oidc-provider/oidc.eks
            ap-northeast-2.amazonaws.com/id/252FEB----------------98FF9E7"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    // "oidc.eks.ap-northeast-2.amazonaws.com/id/252FEB------------98FF9E7:aud": "sts.amazonaws.com", 이부분 대치
                    "oidc.eks.ap-northeast-2.amazonaws.com/id/252FEB-------------98FF9E7:sub":"system:serviceaccount:kube-system:aws-load-balancercontroller"
                }
            } 
        } 
    ]
}
  • oicd 역할 부여시 신뢰관계 수정
  • ALB에 Public ip가 배정되지 않는 오류가 생길 수 있음(oicd 없을 시)
  • 나머지 부분은 AWS 공홈에 나와있는대로 하면 되지만 이부분은 누락되어 있었음

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: istio  # 주석이 중요함
  name: ingress
spec:
  rules:
  - host: httpbin.example.com
    http:
      paths:
      - path: /status/*
        backend:
          serviceName: httpbin
          servicePort: 8000

image

  • aws alb를 설치하지 않아도 istio가 ip를 잡아줌

image

  • 모니터링의 경우 istio의 하위 프로젝트를 사용하면 http Request를 추적할 수 있다.
  • AWS에서 제공해주는 Prometheus는 각 서비스나 파드의 로그를 추적할 수 없음
  • 각 Container의 시스템 로그는 추적할 필요가 없다고 판단함

node {
    stage('Clone repository') {
        checkout scm
    }

    stage("Build image") {
        sh "mvn spring-boot:build-image -D spring.profiles.active=prod -D S3_BUCKET_NAME=loljoa-resource-bucket"
    }
    
    stage("Docker login") {
        environment {
            DOCKER_HUB_LOGIN = credentials('docker-hub')
        }
        sh 'docker login --username=$DOCKER_HUB_LOGIN_USR --password=$DOCKER_HUB_LOGIN_PSW'
    }

    stage("Image rename") {
        sh "docker tag lck_schedule_apiserver:0.0.1-SNAPSHOT loljoa/lck_schedule_apiserver:0.0.1-SNAPSHOT"
    }


    stage("Image push") {
        sh "docker push loljoa/lck_schedule_apiserver:0.0.1-SNAPSHOT"
    }

    stage("Resource cleanup") {
        sh "docker image prune -a"
    }
}
  • jenkins 사용 시 jenkins 유저로 sh를 통해 명령어들을 실행시키기 때문에 package-manger를 통해 설치된 명령어 외에 bashrc에 설정된 변수는 적용이 되지 않는다.
  • 따라서 jenkins의 빌드 환경은 해당 UI를 통해 설정하여야 한다.
  • Maven에서 제공하는 docker image build tool은 test까지 진행된다. 테스트 실패 시 빌드가 진행되지 않을 수 있다.
  • 또한 Jenkins 설치 시 반드시 공식 홈페이지를 참고하자 그렇지 않으면 설치 후 필요한 작업들 수행에 차질이 생겨 정상적 사용이 불가능 할 수 있다.

BackEnd

@RestController
@RequestMapping("/api/account")
@RequiredArgsConstructor
public class HealthController {
    private final ApplicationAvailability availability;
    private final ApplicationEventPublisher eventPublisher;
    private final LocalHostService localHostService;

    @GetMapping("/hello")
    public String hello() {
        return "Application State\n" +
                "Liveness: " + availability.getLivenessState() + "\n" +
                "Readiness" + availability.getReadinessState();
    }

    @GetMapping("/block")   //Readiness Block
    public String block() {
        AvailabilityChangeEvent.publish(eventPublisher, this, ReadinessState.REFUSING_TRAFFIC);
        return "Blocked requests " + localHostService.getLocalHostInfo();
    }

    @GetMapping("/turnoff")   //Liveness Block
    public String turnoff() {
        AvailabilityChangeEvent.publish(eventPublisher, this, LivenessState.BROKEN);
        return "Broken " + localHostService.getLocalHostInfo();
    }

    @Async
    @EventListener
    public void onStateChanged(AvailabilityChangeEvent<ReadinessState> readiness) throws InterruptedException {
        System.out.println("State is changed to " + readiness.getState());
        if(readiness.getState().equals(ReadinessState.REFUSING_TRAFFIC)) {
            Thread.sleep(15000L)
            AvailabilityChangeEvent.publish(eventPublisher, this, ReadinessState.ACCEPTING_TRAFFIC);
        }
//        Action When State Changed
    }
}
  • http Request로 Liveness, Readiness 상태를 바꿀 수 있음
  • @Async 어노테이션을 붙히지 않으면 block을 하고 15초(sleep) 후에 Liveness가 응답을 받는 이상한 동작이 관측됨
    • 이벤트 발생 쓰레드와 처리 쓰레드가 같아서 생기는 문제

apiVersion: v1
kind: Secret
metadata:
  name: ca-key-pair
  namespace: sandbox
data:
  tls.crt: LS0tLS1CRUdJTi...
  tls.key: LS0tLS1CRU...
  • Istio Ingress에 TLS를 적용하려 했으나 OpenSSL로 만든 인증서는 Issuer가 제대로 작동하지 않아서 포기하였음
$ kubectl get issuers ca-issuer -n sandbox -o wide
NAME          READY   STATUS                AGE
ca-issuer     True    Signing CA verified   2m
  • 이 부분에서 에러가 생김

@Override
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Retryable(value = SQLException.class, backoff = @Backoff(delay = 500))
public AccountDto.BettingData bettingToChoice(Long choiceId, Long accountId, Long leagueId, Long gameId, Long point) {
    BettingChoice choice = bettingChoiceRepository.getChoiceById(choiceId);
    Account better = accountRepository.getAccountById(accountId);
    League league = leagueRepository.getLeagueById(leagueId);
    BettingGame bettingGame = bettingGameRepository.getGameDataById(gameId);

    choice.addTotalPoint(better.getUsername(), point);
    better.usePoint(point);
    bettingGame.addTotalPoint(point);
    bettingStateRepository.save(new BettingState(choice, better, league,point));

    return new AccountDto.BettingData(
            gameId,
            league.getLeagueName().split("vs")[0],
            league.getLeagueName().split("vs")[1],
            league.getWeekNum(),
            league.getStartTime(),
            choice.getChoiceId(),
            choice.getName(),
            choice.getTotalPoint(),
            bettingGame.getTotalPoint(),
            point
    );
}
  • 클래스에 @Transaction을 걸어 놓은 상태로 실제 많은 복제 파드에서 하나의 column에 접속할 때 Transaction DeadLock일 걸리는 현상이 발생
  • 1000 이상의 동시 접속 시 완전히 Deadlock이 없어지지는 않았지만 어느정도의 Dead Lock은 해결 되었음(Error율 60% -> 20%)
  • 주로 update 쿼리에서 데드락이 발생
    • 하나의 User에서 사용해서 실 사용과는 거리가 있을 수 있음

결과

  • 예상과 다르게 파드가 늘어날 수록 전체적인 성능이 저하되는 모습이 관측되었음


  • 하나의 node에 파드가 집중되어서 생기는 문제일 수 있다고 하였으나 전체적으로 노드가 골고루 사용되고 있었음
  • 팀원들은 DB에서 동시에 다수의 쿼리가 날아왔을때 리소스 부족으로 일어나는 현상으로 예측하고 있음
    • 실제로 DB의 리소스 유형을 변경 했을 때 scaling된 파드의 경우 성능이 증가되는 폭이 컸음

추후 과제 및 후기

후기

무조건 k8s를 적용하는 것이 좋다라고는 볼 수 없을 것 같다.

  1. 초기 비용이 많이 든다.
  2. 하드웨어 자원(db, chache)을 k8s에 맞게 잘 조정해야만 성능 향상을 얻을 수 있다.
  3. 트랜잭션 처리가 힘들다.(실제로 transaction이 분산되어 있음)

Jenkins 사용 시 빌드에 들어가는 운영 비용이 크게 감소 되었다.

  1. pipeline을 구축하는 것이 조금 힘들긴 하지만 수정 후 빌드에 들어가는 비용을 생각하면 매우 효율적인 tool이라 판단됨
  2. pipeline 구축에 필요시 되는 문법에 대한 reference가 매우 부족하다. 따라서 모르는 부분은 해결하기가 매우 힘들다.

추후 과제

db 클러스터링

  • DB의 성능이 서버의 성능과 매우 밀접한 관련이 있는것 같다 만약 여러 파드가 하나의 DB를 공유할 경우 DB의 thread pool이 부족해지는 현상이 생기면 서버의 성능이 떨어지는 문제가 생김

완벽한 배포 pipeline 구축

  • 이번 프로젝트에서는 수정 -> git push -> 자동 빌드(테스트) -> 배포의 과정을 거쳤지만 단위 테스트에 대한 부분이 매우 부족하였다. 테스트 흐름을 잘 짜서 좀더 정밀하고 완벽한 배포 흐름을 구축할 것

로그(EFK) 저장

  • 많은 테스트를 거쳤지만 일일히 파드를 kubectl로 확인하여 log를 확인하였다. 하지만 대용량으로 갈수록 이러한 방식은 전혀 사용할 수 없을 것 같다고 생각함
  • EFK를 통해 인덱싱, 검색등을 사용하면 로그를 추적하기 좋을 것 같음

참고자료

profile
옛날 블로그 주소 : https://jeongjin984.github.io/

0개의 댓글