문제점

기존 프론트엔드 서버 배포 시, Nginx 버전을 1.19 버전으로 했었음.

 

위와 같이 멀티스테이지 빌드를 활용해 컨테이너 실행 시 , Nginx와 함께 프론트엔드 서버를 띄웠다.

 

하지만 서버 인스턴스 유형을 c6g 시리즈로 변경함에 따라 cpu 아키텍처도 arm64로 변경되었다.

 

AWS ECS 클러스터에서 등록할 컨테이너 인스턴스 오토 스케일링 그룹의 인스턴스 유형을 c6g.2xlarge 변경해주었다. 

 

하지만, 인스턴스 유형이 변경되어 프론트엔드 서버에서 백엔드 서버로 데이터를 받아오지 못하는 에러가 발생했다.(심지어 Status는 500)

 

해결

 

배포된 서버의 라우팅 테이블, NACL, 보안그룹을 다 살펴보았지만 이상이 없었고, 여러 삽질을 거치던 와중..

 

도커 허브에서 제공되는 Nginx 공식 이미지를 찾아보았다.

현재 도커 허브에서 제공되는 Nginx 공식 이미지는 1.24 버전부터 arm64 아키텍처를 지원하고 있어서 안 됐던 것이다..

 

따라서 Dockerfile에서 Nginx 버전을 arm64와 호환되는 버전인 1.25 버전으로 명시해주니 

 

arm64 아키텍처에서 Nginx가 이상을 일으키지 않았다 ..!! 

'Nginx' 카테고리의 다른 글

Nginx에 대하여  (0) 2023.07.29

프로젝트 전체 아키텍처.

신규 프로젝트를 진행하며, 더 이상 온프레미스 서버에서 관리하지 않고, 클라우드 컴퓨팅, 컨테이너 환경을 선택했다.

 

고가용성과 오토 스케일링, 또한 여러 컨테이너 환경을 첫 운영함에 있어서 쿠버네티스 , 도커 스웜, AWS ECS를 고려했다.

 

쿠버네티스와 도커 스웜 역시 강력한 컨테이너 오케스트레이션 도구였지만, AWS ECS 클러스터 인프라에서 배포 컴퓨팅 환경으로

 

서버리스 컴퓨팅 형태인 Fargate를 사용할 수 있다는 장점으로 인해 AWS ECS를 선택, 그에 맞는 인프라를 구축하기로 하였다.   

 

 

Route53

Amazon Route 53은 가용성과 확장성이 뛰어난 DNS 웹 서비스이며, 사용자 요청을

 

AWS 또는 온프레미스에서 실행되는 인터넷 애플리케이션에 연결한다.

 

위 그림과 같이 DNS로 도메인을 별도 구입하여 해당 도메인의 라우팅 경로를 VPC내에 있는 로드 밸런서의 ARN으로 연결 시켜주었다.


Application Load Balancer(ALB)

해당 로드 밸런서의 리스너는 두 가지 경우를 만들어 주었다.

 

HTTP:80

  • HTTP 프로토콜의 기본 포트인 80으로 접근 시, HTTPS:443(프로토콜:포트)를 리다이렉션 대상으로 삼아 주었다.

  • 이로써 http 프로토콜로 접속해도 보안 접속이 가능해졌다.

 

HTTPS:443

  • 리스너 규칙으로 접근하려는 호스트의 헤더에 api.도메인.com가 붙여져있다면 백엔드 대상 그룹으로 라우팅 경로를 지정 .

  •  그 외의 요청은 프론트엔드 대상 그룹으로 라우팅 경로를 지정
로드밸런서의 대상 그룹은 ECS에서 테스크 서비스가 배포되는 시점에서 선택을 할 수 있다.

예를 들어, 백엔드 서비스가 포함된 컨테이너 배포시, 기존에 생성한 로드 밸런서의 대상 그룹 A를 지정해준다면,

배포된 컨테이너 서비스가 자동으로 로드밸런서의 백엔드 대상 그룹으로 지정돼, 라우팅을 수신 할 수 있게 된다.
  • 추가로 트래픽 분산 알고리즘과, 각 대상 그룹에 등록된 서비스의 가중치 지정이 가능하다.

ECS Cluster

서버 자원에 대한 인력 리소스를 줄이기 위해, (ECS를 선택한 이유인) Fargate 컴퓨팅 유형으로 클러스터를 생성해주었고,

 

해당 클러스터에서 각각 프론트엔드 서비스 , 백엔드 서비스를 배포 하였다.

테스크 정의

테스크 정의의 규격을 vCPU 2, 메모리를 16GB로 리소스를 한정하고,

 

오토 스케일링 규칙을 해당 테스크가 로드밸런서의 트래픽 량에 따라

 

최소 3개, 최대 6개까지 유지 될 수 있도록 하여 트래픽 급증에 대비하여 유연하게 서비스를 운영할 수 있게 하였다.

 

cpu 아키텍처는  X86_64로 설정하였으며, 이는 곧 후에 기술하게 될 GitActions의 도커 이미지 빌드 아키텍처와 맞춰주기 위함이다.


또한 서비스 배포 옵션을 상단의 사진과 같이 최소 실행 작업 비율을 100% , 최대 실행 작업 비율을 200%로 하여

 

롤링 업데이트 방식으로 무중단 배포를 할 수 있었다.

 

(본래는 블루/그린 배포 방식으로 하려 했으나, 또 다른 서버 자원의 비용을 감내해야 했기 때문에 배포 속도가 좀 느리더라도 

 

롤링 업데이트 방식을 채택했다..흑)

 


  • 해당 테스크 스케줄러가 각 서비스 배포 서브넷은 로드 밸런서가 위치한 서브넷과 동일하게 맞춰 주었으며,

  • 로드밸런서도 미리 만들어둔 것으로 지정해주었다.
    -> 이 로드밸런서의 트래픽 량에 따라 테스크 실행 갯수가 조절이 된다.

GitActions를 통한 배포 자동화

배포 자동화를 구축하기 위해, 방법을 모색하던 중 이미 GitActions 배포 템플릿에서 ECS 배포용 yaml 템플릿이 있어 

 

해당 yml 파일을 입맛에 맞게 커스텀 해주었다.

 

각 블록을 살펴보자

 

name: Deploy to Amazon ECS

on:
  pull_request_target:
    types:
      - closed
      
  # 이 옵션을 통해 사용자가 직접 Actions를 통해 워크플로우 실행이 가능 !
  workflow_dispatch:
  
env:
  AWS_REGION: ap-northeast-2                  # set this to your preferred AWS region, e.g. us-west-1
  ECR_REPOSITORY: # set this to your Amazon ECR repository name
  ECS_SERVICE:  # set this to your Amazon ECS service name
  ECS_CLUSTER:  # set this to your Amazon ECS cluster name
  ECS_TASK_DEFINITION:  # set this to the path to your Amazon ECS task definition
                       # file, e.g. .aws/task-definition.json
  CONTAINER_NAME: backend # set this to the name of the container in the

name 블록은 워크 플로우 이름

 

on

 

PR이 머지 됐을 경우에만 배포가 될 수 있도록 설정.

 

workflow_dispatch

 

위에서 워크 플로우 배포를 실행 하기 위하여 반드시 PR을 머지시켰어야만 했는데, 

이를 생략하고 바로 실행할 수 있게 하기 위해 해당 옵션을 지정해주었다.

 

env

배포할 서비스 리전,
도커 이미지 프라이빗 레포지토리 ARN,
ECS 서비스 이름,
ECS 클러스터 이름,
ECS  테스크 정의 경로,
배포할 컨테이너 이름을 지정해주었다.

 

# 워크 플로우 종료 후, 배포 결과를 슬렉으로 알리기 위해 필요한 권한 설정
permissions:
  contents: read
  actions: read

jobs:
  deploy:
    if: |
      # 풀리퀘스트 머지시에만 job 실행
      github.event.pull_request.merged == true &&
      # 레포지토리가 fork된 레포지토리가 아닌, 원본 레포지토리일 경우에만
      github.repository == ${{ secretes.REPOSITORY_NAME }} &&
      github.event.pull_request.base.ref == 'main'
    
    # job 이름 설정
    name: Deploy
    # job 실행 환경 설정
    runs-on: ubuntu-latest
    # 배포 환경
    environment: production

    steps:
    - name: Checkout
      uses: actions/checkout@v3
      
    # GitActions에 ECS 서비스 배포 권한 및 ECR에 푸쉬 할 수 있는 권한을 가진
    # IAM 유저의 액세스 키와, 시크릿 키 설정을 해준다.
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ${{ env.AWS_REGION }}

    - name: Login to Amazon ECR
      id: login-ecr
      uses: aws-actions/amazon-ecr-login@v1
      
    # 도커 이미지 빌드 시, 필요한 환경변수는 이미 .gitignore로 제외 되었기 때문에
    # 이미지 빌드 전, repository Secrets에 애플리케이션 환경 변수를 저장 후 생성해준다. 
    - name: make application.yml
      if: contains(github.ref, 'main')
      run: |
        cd ./src/main/resources
        touch ./application.yml
        echo "${{ secrets.ENVIRONMENT_MAIN }}" > ./application.yml
      shell: bash
      
    # 프로젝트 루트 경로의 도커파일에 맞게 이미지 빌드 후, ECR에 푸쉬
    - name: Build, tag, and push image to Amazon ECR
      id: build-image
      env:
        ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        IMAGE_TAG: ${{ github.sha }}
      run: |
        '''
        도커 이미지 빌드 명령어
        도커 이미지 ECR 푸쉬 명령어
        '''
	
    # 배포될 테스크 정의 파일 경로 지정
    - name: Fill in the new image ID in the Amazon ECS task definition
      id: task-def
      uses: aws-actions/amazon-ecs-render-task-definition@v1
      with:
        task-definition: ${{ env.ECS_TASK_DEFINITION }}
        container-name: ${{ env.CONTAINER_NAME }}
        image: ${{ steps.build-image.outputs.image }}
	
    # ECS 테스크 정의 배포
    - name: Deploy Amazon ECS task definition
      uses: aws-actions/amazon-ecs-deploy-task-definition@v1
      with:
        task-definition: ${{ steps.task-def.outputs.task-definition }}
        service: ${{ env.ECS_SERVICE }}
        cluster: ${{ env.ECS_CLUSTER }}
        # 배포한 서비스가 상태가 확인될 때 까지 대기
        wait-for-service-stability: true
        
    # 배포 결과 슬렉 전송
    - name: action-slack
      uses: 8398a7/action-slack@v3
      with:
      	# 프론트엔드와 백엔드 레포지토리가 분리 되어있기 때문에 따로 설정해준다.
        status: ${{ job.status }}
        author_name: 백엔드 배포 결과
        fields: repo,message,commit,author,action,eventName,workflow
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} # required
      if: always()

 

위와 같이 yml 파일로 배포 자동화를 설정 후에 배포 자동화가 무사히 잘 이뤄진 것 까지 확인해보았다.

 

문제점

GitActions에서는 배포 때 마다, 매번 새로운 실행환경을 생성하니 도커 빌드의 캐시를 제대로 활용하지 못하고 있었음

 

이는 곧 매 배포 시마다 새롭게 이미지 빌드를 하며 배포 시간이 늘어남

 

해결방안 : GitActions에서의 도커 이미지 레이어 캐시 활용

레이어 캐시를 사용하는 step을 추가해주면 같은 레이어를 다시 빌드하는데 드는 시간을 줄일 수 잇다.

 

이전 빌드의 캐시를 복원하는 동시에 (커밋마다) 새 캐시를 저장하게 된다.

- name: Cache Docker layers
      uses: actions/cache@v2
      with:
        path: /tmp/.buildx-cache
        key: ${{ runner.os }}-buildx-${{ github.sha }}
        restore-keys: |
            ${{ runner.os }}-buildx-

 

 

path 블록

캐시할 디렉토리를 지정하며, '/tmp/.buildx-cache'는 Docker Buildx가 이미지 레이어를 저장하는 임시 경로이다.

 

key 블록

캐시의 고유한 키를 설정한다. 캐시 키 ${{ runner.os }}-buildx-${{ github.sha }}와 같이 지정된다.

여기서 runner.os는 GitHub Actions 러너의 운영 체제를 나타내고, github.sha는 현재 커밋의 SHA를 나타낸다.

이렇게 하면 각 커밋마다 새로운 캐시가 생성되므로, 도커 레이어가 변경될 때마다 새로운 레이어가 캐시에 저장된다.

 

restore-keys 블록

이전 캐시를 찾을 때 사용되는 키의 패턴, key 값으로 캐시를 찾지 못할 경우 해당 블록에서 지정한 패턴에 맞는 가장 최근의 캐시를 사용한다.

위의 경우(${{ runner.os }}-buildx-)에는 이전에 빌드한 모든 Docker 레이어 캐시를 검색하는 데 사용된다.

'AWS' 카테고리의 다른 글

IAM  (0) 2024.03.30
AWS Cloud Support Associate 1차 면접 후기  (10) 2023.12.12

1. 클라우드 컴퓨팅이란 무엇인가?

구성 가능한 컴퓨팅 자원(예: 컴퓨터 네트워크, 데이터 베이스, 서버, 스토리지, 애플리케이션, 서비스)에 대해 어디서나 접근 할 수 있는, 주문형 접근(on-demand availability of computer system resources)을 가능케하는 모델이며 최소한의 관리 노력으로 빠르게 예비 및 릴리스를 가능케 한다.

 

2. 스케일 아웃(Scale-out)과 스케일 업(Scale-up)의 차이를 설명하라.

인프라 업그레이드를 위한 두 가지 방안으로,

스케일 업은 기존 서버의 자원 및 성능을 보다 업그레이드 하는 것을 의미한다.

단 적인 예로, 자원 및 성능 증강을 목적으로 서버의 디스크를 직접 구매해 추가하거나 , 동일한 방법으로 CPU나 메모리를 추가로 장착해 업그레이드 시킨다.

 

스케일 아웃

기존 서버만으로 요청이나 성능의 한계가 도달했을 때, 서버를 더 증설해 처리할 수 있는 양을 더 늘리거나,

한 서버에 무리가 가지 않도록 부하를 분담 할 수 있다.

 

EC2 에서는 Auto Scaling 그룹이 있으며 ECS 클러스터 생성시 지정해줄 수도 있다. (최소 갯수, 최대 갯수 지정 가능)

스케일 아웃 기준은 로드밸런서 부하량, CPU 사용량, 메모리 사용량 등이 있으며 필요에 따라 선택할 수 있다.

3. MSA(Micro Service Architecture)의 개념을 설명하라.

마이크로서비스란 작고, 독립적으로 배포 가능한 각각의 기능을 수행하는 서비스이다.

마이크로서비스로 독립적으로 실행되는 각 서비스들은 서로의 서비스에 영향을 끼치지 않으며,

각각 배포 및 관리가 가능하고, 각각 다른 기술 스택(개발 언어, 데이터베이스 등)이 사용 가능하다.

리눅스 컨테이너 기술이 핵심이다.

 

4. MSA의 장점은 무엇인가? 기존 방식에 비해 어떤 Benefit을 가져올 수 있는가? 그리고 그에 따른 단점이나 리스크가 있는가?

각각 독립적으로 실행되는 서비스를 통해, 하나의 서비스가 장애를 일으켜도 SPoF(단일 장애점)을 회피 할 수 있고,

동일한 서비스를 컨테이너화하여 여러 환경에 배포 및 운영을 하며 고가용성을 유지 할 수 있다는 장점이 있다.

또한 서비스 별로 필요한 기술 스택을 달리 할 수 있다.

 

대표적인 단점으로는 수많은 마이크로 서비스를 관리할 수 있는 운영 도구가 있다지만, 실제로 서비스 간 통신이라던지 실제 요구 사항에 맞는 서비스를 분할하며 아키텍쳐를 설계한다는 게 단점인 것 같다. 또한 서비스 아키텍처에 대해 러닝커브가 높고 많아지면 많아질수록 전체 서비스에 대한 복잡도가 올라갈 수 있다.

 

5. 컨테이너란 무엇인지 설명하라.

소프트웨어 서비스를 실행하는 데 있어 , 필요한 특정 버전의 프로그래밍 언어 런타임 및 라이브러리와 같은 종속성과 애플리케이션 코드를 함께 포함하는 경량 패키지라고 할 수 있다.

 

운영체제 수준에서 호스트 OS의 커널을 통해 CPU, 메모리, 스토리지, 네트워크 리소스를 호스트 환경의 프로세스와 동일하게 취급당하며

또한 실행 환경에서 애플리케이션을 추상화 할 수 있는 논리 패키징 메커니즘을 제공한다.

 

6. 컨테이너를 위한 운영 환경에는 어떠한 것들이 있는가? 가장 많이 사용되는 것은 무엇인가?

대표적인 컨테이너 오케스트레이션 도구로는 도커 스웜, 쿠버네티스 , 도커 컴포즈가 있으며, 가장 많이 사용되는 것은 쿠버네티스라 할 수 있다.

7. 쿠버네티스가 가장 선호되는 이유가 무엇이라고 생각하는가?

아무래도 쿠버네티스는 주요 클라우드 서비스에서 매니지드 서비스 형태로 제공되는 게 큰 이유인 것 같다.

명령행 도구로 명령을 입력하기만 해도 여러 대의 노드를 갖춘 쿠버네티스 클러스터를 즉석에서 생성할 수 있기 때문이고, 또한 노드 역할을 하는 가상 머신의 관리까지 맡아준다. 또한 쿠버네티스에선 스웜과 달리 세세하게 설정할 수 있는 기능이 많기 때문이기도 하다. 예를 들어 블루-그린 배포나 자동 스케일링, 역할 기반 접근 제어 같은 기능을 쿠버네티스에 쉽게 적용이 가능하다.

8. 쿠버네티스 클러스터의 기본 아키텍처에 대해 설명하라.

마스터 노드와 워커 노드로 구성되며 마스터 노드는 배포할 서비스 스케줄링, 워커 노드 갯수 관리, API 엔드 포인트 제공 등을 담당하며,

워커 노드는 여러 개의 파드로 구성되며 각 파드는 여러 서버로 구성될 수 있고 각각 여러 컨테이너들을 함께 실행이 가능하다.

9. 모니터링 툴을 사용해본 적이 있는가? 있다면 그에 관해 설명하라.

오픈소스 APM으로 핀포인트를 사용해본 경험이 있다. 핀포인트는 애플리케이션의 코드에 직접 수정을 하지 않고, BCI ( Byte Code Instrumentation ) in Java로 클래스 로드 시점에 애플리케이션 코드를 가로채 성능 정보와 분산 트랜잭션 추적에 필요한 코드를 주입하는 것이었다. 그렇게 추적된 트랜잭션은 요청이 들어올 시 실시간으로 확인이 가능하며, 특정한 시간대에 모든 요청들의 샘플링도 할 수 있을 뿐더러,  콜스택을 자세하게 볼 수 있는것이 흥미로웠다.

10. 쿠버네티스에서 Auto Scaling의 원리에 대해 설명하라.

애플리케이션의 부하나 트래픽 증가에 따라 인스턴스 수를 동적으로 조절한다. 쿠버네티스는 주어진 조건으로 각 파드의 리소스 사용량을 모니터링하고 필요한 경우 파드의 복제본 수를 조절한다. 예를 들어 CPU 사용률이 높아지면 쿠버네티스 컨트롤 플레인은 파드의 복제본 수를 늘려서 자동으로 부하를 분산시켜준다. 이때 , 새로운 파드를 생성하거나 기존 파드를 삭제 할 수도 있다.

컨트롤 플레인이 스케일링 결정을 내린 후, 각 연결된 워커노드에 파드의 크기를 조절하는 작업을 수행한다.

 

 

질문 출처 : https://artist-developer.tistory.com/15

Nginx? 동시접속 처리에 특화된 웹 서버 프로그램

  • 기존의 Apache Web Server(이하 아파치)는 꽤나 오래전에 탄생(1995년)에 하였음

  • 클라이언트 요청 하나당 connection을 만들어 요청을 처리하는 구조였음
    → connection을 생성하기 위해 서버의 프로세스를 생성함

  • 이 프로세스를 만드는 작업이 꽤나 오래걸리는 작업이었기 때문에 미리 프로세스를 만든 후, 요청이 들어오면 해당 프로세스를 사용하는 방식이었음

  • 그러나 시대가 변하며 clients의 요청이 폭발적으로 많아지며 C10K(connection 10000)의 문제가 발생하게 됨
    • connection 마다 프로세스가 하나씩 맡다 보니, 이는 곧 메모리 부족 및 CPU 자원을 너무 많이 할당하게 됨 → 더이상 프로세스를 생성할 수 없음

    • (심지어 요청당 connection의 유효시간까지 있게 되니…)

비동기식 이벤트 기반 접근 방식

 

Master Process Worker Process (실제로 일 하는 프로세스)
- 엔진 엑스 설정 파일을 읽고, 유효성을 검사

- Worker Process를 생성하고 관리.

- Worker Process의 갯수는 설정 파일에서 정의됨.

- 미리 워커 프로세스의 개수를 지정하지 않으면 사용가능 한 CPU 코어의 개수의 맞게끔 자동으로 개수를 조정해줌.
- 생성된 워커 프로세스는 각자 지정된 listener 소켓을 배정 받아 새로운 클라이언트 요청이 들어오면 커넥션을 형성하고 처리함.

- 커넥션은 지정된 Keep Alive 시간만큼 유지되며, 이 시간 동안은 이전 클라이언트 요청을 계속 처리 할 수 있음

- 새로운 요청이 들어오면 새로운 커넥션을 형성하거나, 이미 만들어진 다른 커넥션으로부터 들어온 요청을 처리함.
  • 즉, 각 요청에 대해 다른 전용 프로세스 또는 스레드를 생성하는 대신, 하나의 워커 프로세스에서 여러 연결 및 요청을 처리한다.

  • 일반적으로 워커 프로세스는 CPU 코어당 하나가 생성되기 때문에 기존 아파치 서버보다 훨씬 적은 메모리가 사용되며, 작업 전환에 CPU 주기가 낭비 되지 않는다.(context switching 감소)

  • 수백만 건의 동시 요청을 성공적으로 처리하고 매우 확장이 잘 됨

NGINX 워커 프로세스 내부

엔진엑스가 비동기 처리 방식 구조로 한 개 또는 고정된 프로세스만 생성하여 사용.

  • 각 작업자 프로세스는 마스터 프로세스(Nginx 구성 기반)구성으로 초기화 되며, 마스터 프로세스에서 리스너 소켓과 함께 제공되며,
  • 이벤트를 기다리는 것으로 시작된다.
    • 작업을 받는 역할 → 리스너 소켓
  • 각 연결은 프로토콜마다 각각 맞는 상태 시스템에 연결된다.

  • HTTP 상태 시스템이 가장 일반적으로 사용되지만 NGINX는 스트림(원시 TCP) 트래픽 및 여러 메일 프로토콜에 대한 상태 시스템도 구현한다.
    • 여러 프로토콜을 다 처리할 수 있다는 얘기(하나의 작업 프로세스에서)

결국 워커 프로세스의 작동 순서는

1. 워커 프로세스는 리스너 및 커넥트 소켓에서 이벤트(사용자 요청)를 기다림

 

2. 소켓에서 이벤트가 발생하고 이를 처리함 

 

이때, queue를 확인 한 뒤, 새로운 이벤트가 있다면 처리함 (loop)

  1. 많은 이벤트를 수신한 다음 필요한 작업을 수행하며 하나씩 처리하게 됨,
  2. 따라서 모든 처리는 하나의 스레드에 있는 큐에 대한 루프에서 수행된다.
    • 대부분의 경우 매우 빠르며 엔진엑스는 즉시 대기열의 모든 이벤트를 처리한다.

 

스레드 풀

  • 그러나 위의 비동기 처리 방식에서 문제점이 하나 있다. (비단 모든 비동기 처리 방식의 쟁점이겠지만..)
    • 바로 블로킹의 발생 → 무거운 작업이 발생하면 상당한 시간 동안 이벤트 처리 주기를 중지해버림(ex: Disk I/O)
    • (동기 방식으로 데이터베이스에서 응답을 받는 경우) → 액세스까지 시간이 걸릴 수 있음
    • 이러한 작업을 처리하는 동안 워커 프로세스가 다른 작업을 수행할 수 없으며 사용 가능한 시스템 리소스가 더 있고,
    • 큐의 이벤트가 해당 리소스를 사용할 수 있더라도 다른 이벤트를 처리할 수 없게됨.

이해를 돕기 위한 예시   🤩

  • 위와 같이 가게 앞에 대기하고 있는 손님이 많은 영업사원을 생각해보자.
  • 대기열,기다리는 줄(큐)의 첫 번째 사람은 매장에 없는 창고에 있는 것을 요구한다.(무거운 작업)
  • 영업 사원은 상품을 배달하기 위해 창고로 이동하는데, 이 때 대기열의 사람들은 영업 사원이 올 때까지 기다려야한다.

  • 이것과 거의 흡사한 상황이 엔진엑스에서 메모리에 캐시 되어 있지 않은, 디스크 I/O 작업 요청 상황이다.
  • 대기열(큐)에 있는 다른 작업들은 디스크 작업이 필요하지 않을 수 있지만, 해당 무거운 작업이 끝날 때까지 강제로 기다려야 한다.
  • 결과적으로 대기 시간이 증가하고 시스템 리소스가 완전히 활용되지 않는다.

 

이해를 돕기 위한 예시 2

  • 스레드 풀을 사용하며 영업 사원은 더 똑똑 해졌다.
  • 전과 같은 상황으로, 손님이 매장과 멀리 떨어져있는 창고의 상품을 요청할때, 배송사원은 요청을 받고
  • 다른 배송 서비스에 손님의 주문을 맡겨버린다 → 배송 서비스에서 주문을 처리함
    • 해당 요청을 한 손님만 주문 완료를 기다리게 됨
  • 영업 사원은 무거운 작업을 다른 배송 서비스에 맡겨버리고 다음 손님을 받게 된다. 
    • NGINX 측면에서 스레드 풀은 배송 서비스의 기능을 수행한다.
    • 작업 대기열과 작업을 처리하는 스레드로 구성된다.
    • 워커 프로세스(영업 사원)이 긴 작업을 수행해야 하는 경우 작업을 자체적으로 처리하는 대신 스레드풀 대기열에 작업을 배치하여 맡긴다.

엔진엑스 역할

1. 정적 파일 처리하는 HTTP 서버로서의 역할

  • HTML, CSS , JavaScript, 이미지와 같은 정보를 웹 브라우저에 전송하는역할을 함
    • 만약 웹 서버를 이용하지 않는다면 요청 때마다 이러한 정보들을 웹 앱 서버에서 제공하기 때문에 시간도 시간이지만, 서버의 부하가 심해짐

2. 응용 프로그램 서버(WAS)에 요청을 보내는 리버스 프록시 역할

  • 리버스 프록시 란 외부 클라이언트에서 서버로 접근 시, 중간에서 중개자 역할을 하여 내부 서버로 접근할 수 있도록 도와주는 서버이며, 이를 활용했을때 얻을 수 있는 장점은 다음과 같다.
    • 보안
      • 외부 사용자로부터 내부망에 있는 서버의 존재를 숨길 수 있다.
      • 모든 요청은 리버스 프록시 서버에서 받으며, 매핑되는 내부 서버로 요청을 전달함.
      • 또한 Nginx는 SSL 설정도 가능하기 때문에, 사용자의 요청과 응답 등과 같은 정보를 암호화해서 통신이 가능하다.
      • 그렇지만, client ↔ Nginx만 SSL 설정을 하고 나머지 업스트림 서버와의 통신은 HTTP 통신으로만 할 수 있다고도 한다.
    • 로드밸런싱
      • 리버스 프록시 서버가 내부 서버에 대한 정보를 알고 있으므로,
      • 각 서버의 상태에 따라 부하를 분산시키며 요청을 전달할 수 있다. (여러 부하분산 알고리즘 사용)

엔진엑스로 정적 컨텐츠 서비스하기

  • 엔진엑스 설치 후 /etc/nginx/conf.d/default.conf에 생성된 기본 HTTP 설정 파일
server {
	listen 80 default_server;
    server_name www.example.com;
    
    location / {
    			root /usr/share/nginx/html;
                # alias /usr/share/nginx/html;
                index index.html index.htm;
    }


}
  • 이 설정은 HTTP 프로토콜과 80 포트를 사용해 /usr/share/nginx/html/ 경로에 저장된 정적 콘텐츠를 제공한다.
    • server블록은 새로운 server 블록을 선언하여, 엔진엑스가 처리할 새로운 컨텍스트를 정의함.
    • listen 80은 엔진엑스가 80 포트로 들어오는 요청을 수신하게 하고, 이 블록에 정의된 내용이 80 포트에 대한 기본 컨텍스트가 되도록 default_server 매개 변수를 사용한다.
      • 예제는 단일 포트만 사용하지만, 필요에 따라 포트 범위를 지정해 줄 수 있다.
    • server_name 지시자에는 서버가 처리할 호스트명이나 도메인명을 지정한다.
      • 만약 설정이 default_Server 매개변수를 통해 지정되지 않았다면, 엔진엑스는 요청 호스트 헤더값이 server_name 지시자에 지정된 값과 같을 때만 server 블록 에 지정된 내용을 수행한다.
      • 즉, 서버가 사용할 도메인이 정해지지 않았다면 default_server 매개변수를 사용해 기본 컨텍스트를 정의하고 server_name 지시자를 생략할 수 있다.
    • location 블록은 URL의 경로를 기반으로 한다.
      • 엔진엑스는 요청된 URI에 가장 적합한 location 블록을 찾는다.
      • root 지시자 는 주어진 컨텍스트에서 콘텐츠를 제공할 때 서버의 어떤 경로에서 파일을 찾을지 알려준다.
      • 엔진엑스는 root 지시자에 정의된 경로에 수신된 URI 값을 합쳐 요청된 파일을 찾는다.
        • root지시자 정의된 경로 + 80포트로 수신된 URI
      • location 지시자에 URI 접두어를 사용했다면 이 값도 root 지시자에 지정한 값과 합쳐진다.
        • 이렇게 동작하지 않도록 하려면 root 지시자 대신 alias 지시자를 사용한다.
    • index 지시자는 URI에 더는 참고할 경로 정보가 없을 때 엔진엑스가 사용할 기본 파일 혹은 확인할 파일 목록을 알려준다.
  •  

'Nginx' 카테고리의 다른 글

Nginx와 arm64  (0) 2023.08.04

모니터링의 필요성

프로젝트를 여러 개 해보며, 본인을 포함한 팀원들이 배포된 프로젝트에 대한 모니터링이 잘 되지 않아,

 

수많은 트러블 슈팅에 많은 난항이 있었다.

 

예를 들어, (배포 환경에서) 특정 부분에서 오류가 발생해 서비스가 잘 동작하지 않는 상황에서,

 

  • 개발자 입장에서는 어디서 어떤 함수가 오류를 일으켰는지,
  • DB의 문제인지 코드의 문제인지..등등을 단번에 파악하기란 매우 어려운 일이다

또한, 향후 진행하게 될 프로젝트에 대해(BtoB든 BtoC) 효과적인 모니터링 시스템이 있으면 좋겠다는 생각을 누구나가 했을 터.

 

그러던 중,,, 네이버에서 만든 모니터링 오픈 소스 핀포인트를 발견 했으며 이는 꽤나 엄청났다.

핀포인트 ?

  • 위와 같이 분산환경에서 애플리케이션 모니터링에 최적화된 기능이 많아 매우 유용하게 사용이 가능하다.
  • 응답 코드는 물론 Response가 얼마나 걸렸는지, 자세히 까보면 어느 함수가 불려서 어떤 쿼리가 실행 되었는지도 자세하게 나온다.

실제 모니터링 화면

  • Java 분산 서비스 및 시스템의 지속적인 성능 분석을 제공하며, 오류 발생 가능성에 대한 진단과 추적을 지원하는 플랫폼 서비스.
  • 분산 애플리케이션의 트랜잭션 분석
    • 지금은 애플리케이션이 하나라 대시보드에 하나만 보이지만, 프로젝트와 핀포인트 에이전트를 동시에 구동할 때
      ( java,
      -javaagent:핀포인트에이전트가 설치된 경로,
      DdpointApplicationName=애플리케이션이름,
      DdpointApplicationId=에이전트id,
      -jar,
      애플리케이션.jar)
    • 위의 명령어의 에이전트 id로 애플리케이션이 여러 서버에 분산 되어 있을 때
    • 애플리케이션이름으로 등록된 에이전트 id를 통해 여러 서버에 있는 애플리케이션도 동시에 확인이 가능함

Deep Dive..

핀포인트 개발 동기

과거 인터넷 서비스는 사용자가 적음과 동시에, 구조 자체가 단순했었다.

 

2계층(웹 서버 + 데이터베이스) 또는 3계층(웹 서버, 웹 애플리케이션 서버, 데이터베이스)로 구성해 서비스를 운영이 가능했음.

 

하지만 인터넷 서비스가 발전하면서 3계층을 넘어 n계층 (multitier) 아키텍처로 변경 되어지며, 마이크로서비스 형식의 아키텍처는 이제 현실이 되어가고 있었다.

 

n계층 아키텍처로 변화함에 따라 시스템의 복잡도도 덩달아 증가하며, 장애나 성능 문제가 발생했을 때 해결이 어려워졌다.

 

따라서 이러한 문제점을 해결하기 위해 네이버에서는 n계층 아키텍처를 효과적으로 추적할 수 있는 새로운 플랫폼을 개발하기로 하였다.

핀포인트 특징

  • 분산된 애플리케이션의 메시지를 추적할 수 있는 분산 트랜잭션 추적
  • 애플리케이션 구성 자동 파악해서 대시보드에 뿌려줌
  • 대규모 서버군을 지원할 수 있는 수평 확장성
  • 뛰어난 가시성으로 문제 발생 지점과 병목 구간을 쉽게 발견

분산 트랜잭션 추적 방법

RPC …?

💡 Remote Procedure Call(원격 프로시저 호출)의 약자로,

별도의 원격 제어를 위한 코딩 없이 다른 주소 공간에서 “함수나 프로시저”를 실행할 수 하는 프로세스 간 통신 기술을 말한다.

→ 프로그래머는 함수가 프로그램이 존재하는 로컬 위치에 있든, 원격 위치에 있든 상관없이 동일한 기능을 수행할 수 있음을 의미.

 

일반적으로 프로세스는 자신의 주소공간 안에 존재하는 함수만 호출하여 실행이 가능함.

 

그러나, RPC의 경우 자신과 다른 주소 공간에서 동작하는 프로세스의 함수를 실행할 수 있게 해주는데,

 

이는 네트워크를 통한 메시징을 수행하기 때문.

 

MSA 구조의 서비스를 만들 때, 언어나 환경에 구애받지 않고, 비즈니스 로직을 개발하는데 집중할 수 있다 !

 

 

Google Dapper의 분산 트랜잭션 추적 방법

분산 트랜잭션 추적의 핵심은 그림처럼 Node1 에서 Node 2로 메시지를 전송했을 때, 분산된 Node1과 Node 2가 처리한 메시지의 관계를 찾아내는 것 !!

 

그러나 , 메시지의 관계를 찾을 때 어려운 점은 Node1이 보낸 N개의 메시지와 Node 2에 도착한 N개의 메시지를 보고,

 

메시지 간의 관계를 엮을 수 있는 방법이 없다는 것이다.

 

즉, Node1에서 X번째 메시지를 보냈을때, Node 2가 받은 N개의 메시지 중 X번째 메시지를 선택할 수가 없다.

 

TCP 프로토콜이나 운영체제의 수준에서 추적하려 했지만 프로토콜마다 별도로 구현해야 해 복잡도가 높고 성능이 좋지 않았다고 한다.

=> 고질적인 문제인 메시지를 정확하게 추적 해결을 하지 못했다고 한다.

 

하지만 Google Dapper팀은 이 문제를 간단한 방법으로 해결했다.

 

메시지 전송 시 애플리케이션 수준에서 메시지를 엮을 수 있는 태그를 추가 한것이다.

 

HTTP를 예로 들면, HTTP 요청 전송 시, HTTP 헤더에 메시지 태그 정보를 넣고 , 이 정보를 메시지 간의 연결 고리로 활용해 메시지를 추적한다.

  • TransacionId(TxId) : 분산된 노드를 거쳐 다니는 메시지의 아이디로, 전체 서버군에서 중복되지 않아야 함.
  • SpanId : RPC 메시지를 받았을 때 처리되는 작업의 아이디를 정의함. RPC가 노드에 도착했을 때 생성.
  • ParentSpanId : 호출한 부모의 SpanId를 나타냄

구성 요소

Pinpoint Agent

  • 애플리케이션의 모니터링 정보를 Collector로 전달
  • 현재 프로젝트에선 해당 Agent는 백엔드 서버 컨테이너가 돌아가는 EC2 서버에 위치한다.
  • EC2에 pinpoint Agent + Spring Project 요렇게 있는 셈.

Pinpoint Collector

  • 위의 Agent 서버에서 받은 정보를 HBase란 곳에 적재 한다.
  • Pinpoint는 코드 수준의 정보를 추적하기 때문에 트래픽이 많으면 많을 수록 데이터의 양이 폭발적으로 증가한다는 단점이 있다.
  • 그래서 핀포인트는 이 정보들을 Hbase에 담아서 활용한다.
Hbase ?
구글의 BigTable을 기반으로 발전한 NoSQL 오픈 소스.

실시간 읽기/쓰기 기능을 제공한다고 한다.

강력하게 일관된 읽기/쓰기고속 카운터 집계와 같은 작업에 매우 적합.

 

현재 프로젝트에서 해당 Collector는 인바운드 규칙이 좀 특이하단 걸 볼 수 있는데, 이는 Agent에서 Collector로 데이터를 보내는 포트가 8000 ~ 9999 번대이기 때문.

Pinpoint Web

  • 적재된 데이터를 웹으로 노출하여 모니터링 제공

문제점

  1. 프로젝트가 위치한 서버는 Serverless 컴퓨터 형태인 Fargate 방식임.
    • EC2 백엔드 서버에 Agent를 설치를 해야 하는데, 접속할 서버가 없음 .
    • Fargate 컴퓨팅 서비스는 그냥 컴퓨터 사양 이것저것 안 만들고 그냥 실행할 도커 이미지만 있으면 실행할 수 있도록 설계 되어 있음.
  2. 클러스터내 지원 컴퓨팅
    • 현재 프로젝트 AWS ECS 클러스터는 이런 상황을 염두에 두지 않고, Fargate 컴퓨팅만 실행하도록 되어 있었음
    • 고로, 기존 클러스터를 전부 다 삭제 하고 새로운 클러스터와 EC2 서버를 입맞에 맞게 생성해야함.
      → 프로젝트 규모에 맞게 …
  3. 도커 기반 환경에서 Pinpoint Agent 설치 …
    • 위에서 나열한 문제점은 어찌저찌 해결이 가능했다.
      1. 기존 클러스터를 삭제하고 Fargate 컴퓨팅과 EC2 컴퓨팅이 둘 다 가능한 새로운 클러스터를 만들었다.
      2. 또한 GitActions에서 우리 프로젝트를 빌드하고 서버에 전달해줄 때는 amd64 기반 이미지라
      3. 해당 CPU 아키텍처에 맞춰 다시 만들어 주었음
    • GitActions에서 이미지를 빌드 하고, AWS ECR에 푸쉬하고 EC2 서버에 배포로 변경
      1. 여기서 제일 큰 문제가 발생하는데

        처음에는 핀포인트 에이전트(수집 서버에 전달할 에이전트) 이미지 레이어를 추가시켜

        애플리케이션의 이미지를 빌드하는 과정에 이를 넣는 방식을 사용하려 했다.

        이렇게 한다면 그냥 도커 이미지만 있어도 알아서 핀포인트 에이전트와 애플리케이션이 통째로 포함된 컨테이너가 실행될테니 말이다.

      2. 하지만 위의 방법은 이미지를 빌드 하는데 너무 많은 시간이 걸렸고,

        이미지의 크기도 너무 증가하여

        결과적으로 배포하는 시간이 오래 늘어나게 되었다.
  4. 모니터링 데이터 누적 수집 불가
    1. 핀포인트가 배포할때마다 이미지 내부에 설치 됨.

    2. 배포가 일어날때마다 모니터링 데이터가 아예 초기화가 되버림

    3. 백엔드 서버가 돌아가는 환경에 자체적으로 설정해놓는게 아닌 이상 데이터를 누적 할 수 없음.

해결방안 - 도커 바인드 마운트

바인드 마운트란 ?

호스트의 스토리지를 컨테이너에 직접적으로 연결할 수 있는 방법.

`바인드 마운트`는 호스트 컴퓨터 파일 시스템의 디렉터리를 컨테이너 파일 시스템의 디렉터리로 만든다.

컨테이너 입장에서는 그냥 평범한 디렉터리에 불과하지만, 

도커를 사용하는 입장에서는 컨테이너가 호스트 컴퓨터의 파일에 직접 접근도 가능하고, 

그 반대도 가능하다.

- 바인드 마운트를 사용하면 호스트 컴퓨터의 파일 시스템을 명시적으로 지정해서 컨테이너 데이터로 쓸 수 있다.
- 속도 면에서 뛰어난 SSD 디스크, 네트워크상에서 사용하는 분산 스토리지까지 
- 호스트 컴퓨터에서 접근 가능한 파일 시스템이라면 무엇이든 컨테이너에서도 사용할 수 있다.
  1. 따라서, 호스트에 설치된 핀포인트 에이전트를 컨테이너가 중단되거나,

    실행될 때 영속성을 유지(호스트 서버의 핀포인트 에이전트 관련 환경 설정 파일 등..) 할 수 있어야 했기 때문에 

    컨테이너의 바인드 마운트 기능을 사용하기로 했으며 곧바로 이를 적용하였다.


  2. 프로젝트가 포함된 이미지가 컨테이너에서 실행 시, 호스트에 설치되어 있는 핀포인트 에이전트 경로를 마운트 시킴
    • 해당 폴더에는 핀포인트 에이전트를 실행하기 위한 .jar 파일과 config 파일 등이 있다

    • 로컬에 있는 /home/ec2-user/pinpoint-agent 를 같이 사용하게 됨

  • 볼륨이름을 pinpoint-agent로 설정해주고, 해당 볼륨을 컨테이너 내부에 /app/pinpoint-agent에 생성하도록 했다.

  • 요렇게 실행시점에 실행 서버 환경의 폴더를 마운트를 시켜놓으면 ,
  • 컨테이너 실행 시점에서 위의 명령어들을 사용해 웹 애플리케이션과 핀포인트 에이전트를 같이 구동하게 된다.
  • 서버 환경의 폴더를 그대로 마운트해서 실행되는 것 !

'Work Experience' 카테고리의 다른 글

AWS EC2 크롤러 컨테이너 헬스체크 적용기  (4) 2023.11.17

실전에서 유용한 표준 라이브러리

itertools 

  • 파이썬에서 반복되는 형태의 데이터를 처리하기 위한 유용한 기능들을 제공
  • 특히 순열과 조합 라이브러리는 코딩 테스트에서 자주 사용됨.
  • 완전탐색 유령에서 코드를 간결하게 표현이 가능

heapq

  • 힙(Heap) 자료구조를 제공함
  • 일반적으로 우선순위 큐 기능을 구현하기 위해 사용됨.
  • 대표적으로 다익스트라 같은 최단경로 알고리즘에서 자주 사용됨

bisect

  • 이진 탐색(Binary Search) 기능을 제공함.

collections

  • 덱(deque), 카운터(Counter)등의 유용한 자료구조를 포함한다.

math

  • 필수적인 수학적 기능을 제공 .
  • 팩토리얼, 제곱근, 최대공약수(GCD), 삼각함수 관련 함수부터 파이와 같은 상수를 포함함

자주 사용되는 내장 함수 ( 잘 몰랐던 거 위주)

# sorted() with key
array = [('홍길동', 35), ('이순신', 75), ('아무개', 50)]
result = sorted(array, key=lambda x: x[1], reverse=True)
print(result)

람다 함수를 사용해 sorted 함수에 매개변수로 전달된 array의 1번째 인덱스 값을 키로 지정하고 , 내림차순으로 정렬을 시킨다.

 

'Programming Language > Python' 카테고리의 다른 글

[ 파이썬 기본문법 ]  (0) 2023.07.16

리스트 컴프리헨션

리스트를 초기화하는 방법 중 하나.

  • 대괄호 안에 조건문과 반복문을 적용하여 리스트를 초기화 할 수 있음
# 0부터 9까지의 수를 포함하는 리스트
array = [i for i in range(10)]
# range라는 함수는 0부터 9까지 i가 순회 할 수 있도록 해줌
# i라는 변수가 0부터 9까지 증가를 할 때마다, 그 i 값을 원소로 설정해서 리스트를 만듬

print(array)
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  • 이렇게 유용한 게 있었다니…
# 0 부터 19까지의 수 중에서 홀수만 포함하는 리스트
array = [i for i in range(20) if i & 2 == 1]

#0 부터 19까지 홀수만 출력 됨
print(array)

# 1부터 9까지의 수들의 제곱 값을 포함하는 리스트
array = [i * i for i in range(1,10)]
# i라는 변수는 1부터 9까지 증가하며 i 값들을 제곱해서 새로운 리스트를 만듬

또한, 리스트 컴프리헨션은 2차원 리스트를 초기화 할 때 효과적으로 사용이 가능

특히 N X M 크기의 2차원 리스트를 한 번에 초기화 해야 할 때 매우 유용함.

  • 좋은 예시 : array = [[0] * m for _ in range(10)] → 반복 될 때 마다, m (열) 크기 만큼 리스트의 원소를 설정

  • n 번 반복을 할 때마다, 배열(가로) 길이가 m인 리스트(내용물이 0)를 생성함

언더바는 언제 사용 하는가?

파이썬에서는 반복을 수행하되, 반복을 위한 변수의 값을 무시하고자 할때 언더바(_)를 자주 사용한다고 한다.

# 1부터 9까지의 자연수를 더하기
summary = 0 
for i in range(1, 10):
		summary += i
# 반복문을 수행하며 , i값을 더하기 위해 
# i 값이 필요하니 변수를 만듦
# Hello World를 5번 출력

for _ in range(5):
		print("Hello World")

# 반복문을 돌며 특정한 값이 필요없고,
# 그냥 반복만 하고 싶을 경우이니 _ 
# 언더바 사용

튜플 자료형

튜플 자료형은 리스트와 유사하지만 다음과 같은 문법적 차이가 있다.

  • 튜플은 한 번 선언된 값을 변경할 수 없다.
  • 리스트는 대괄호 [] 를 이용하지만, 튜플은 소괄호 () 를 이용한다.

튜플은 리스트에 비해 상대적으로 공간 효율적이다.

# 튜플 사용 예제

a = (1, 2, 3, 4, 5, 6, 7, 8, 9)

# 네 번째 원소만 출력
print(a[3])

# 두 번째 원소부터 네 번째 원소까지
print(a[1:4])

튜플내의 값을 변경하려 한다면( ex : a[2] = 7),

tuple 객체의 값은 Immutable(불변)이니 변경하지 못한다고 에러가 뜸

튜플을 사용하면 좋은 경우

  1. 서로 다른 성질의 데이터를 묶어서 관리해야 할 때
    • 최단 경로 알고리즘에서는 (비용, 노드 번호)의 형태로 튜플 자료형을 자주 사용함
    • 예를 들어 , 학생의 학번, 성적과 같이 다른 성질의 데이터를 묶을 때. 다양한 정보를 포함 가능
  2. 데이터의 나열을 해싱의 키 값으로 사용해야 할 때
    • 튜플은 변경이 불가능하므로 리스트와 다르게 키 값으로 사용될 수 있다.
  3. 리스트보다 메모리를 효율적으로 사용해야 할 때 .

집합 자료형

집합은 다음과 같은 특징이 있다.

  • 중복을 허용하지 않음
  • 순서가 없음

집합은 리스트 혹은 문자열을 이용해서 초기화 할 수 있다.

  • 이때 set() 함수를 이용함.

혹은 중괄호안에 각 원소를 콤마(,)를 기준으로 구분하여 삽입함으로써 초기화 할 수 있다.

데이터의 조회 및 수정에 있어서 O(1)의 시간에 처리할 수 있다.

# 집합 자료형 초기화 방법 1
data = set([1, 1, 2, 3, 4, 5 ]) -> 중복이 제거 된 후, 집합 자료형으로 됨.

# 집합 자료형 초기화 방법 2
data = {1, 1, 2, 3, 4, 4, 5}

# 집합 자료형으 ㅣ연산

a = {1, 2, 3, 4, 5}
b = {3, 4, 5, 6, 7}

# 합집합
print(a | b)
# {1, 2, 3, 4, 5, 6, 7}

# 교집합
print(a&b)
# {3, 4, 5}

# 차집합
print( a - b)
# {1, 2}

기본 입출력

input() 함수는 한 줄의 문자열을 입력 받는 함수.

map() 함수는 리스트의 모든 원소에 각각 특정한 함수를 적용할 때 사용.

# 공백을 기준으로 구분된 데이터를 입력 받을 때는 다음과 같이 사용
list(map(int, input().split()))

# 공백을 기준으로 구분된 데이터의 개수가 많지 않다면, 단순히 다음과 같이 사용
a, b, c = map(int, input().split()) -> 바로 a, b, c 변수에 할당 가능

빠르게 입력 받기

사용자로부터 입력을 최대한 빠르게 받아야 하는 경우가 있음

파이썬의 경우 sys 라이브러리에 정의돼 있는 sys.stdin.readline() 메서드를 이용함.

  • 단 입력 후 엔터가 줄 바꿈 기호로 입력되므로 rsstrip() 메서드를 함께 사용함.

조건문과 반복문

조건문에서 아무것도 처리하고 싶지 않을때 pass 키워드를 사용한다고한다.

예시 ) 디버깅 과정에서 일단 조건문의 형태만 만들어 놓고 조건문을 처리하는 부분은 비워놓고 싶은 경우

score = 85

if score >= 80:
  pass # 나중에 작성할 소스코드
else:
  print('성적이 80점 미만입니다')

print('프로그램을 종료합니다')

# 결과 : 프로그램을 종료합니다
# 파이썬 조건문 내에서으 ㅣ부등식

x = 15
if 0 < x < 20:
	print()

# 요런것도 가능하다구 한당 !

람다 표현식

람다 표현식을 이용하면 함수 간단하게 작성할 수 있음

  • 특정한 기능을 수행하는 함수를 한 줄에 작성할 수 있다는 점이 특징임.
# 람다 표현식으로 구현한 두 매개변수의 합 구하기
# 이름 없는 함수라고도 불림 (ㄷ ㄷ 처음 앎)
print((lambda a, b : a + b)(3,7))
# 예시: 내장 함수에서 자주 사용되는 람다 함수

array = [('홍길동', 50), ('이순신', 32)]
# 각 원소들이 튜플 형태로 구성되어 있음

print(sorted(array, key=lambda x : x[1])
# 어떠한 튜플이나 리스트와 같은 원소가 있을때, 얘의 두번째 (여기서는 점수 -> 1번째 값)
# 튜플의 2번째 값을 기준으로 정렳할 수 있음
# 정렬 기준 (key 속성)를 람다함수를 사용함.
# 여러 개의 리스트에 적용
list1 = [1,2,3,4,5]
list2 = [6,7,8,9,10]

result = map(lambda a, b: a+b, list1, list2)
# map 함수는 각각의 원소에 대해 어떠한 함수를 적용하는 것임
# 따라서 list1과 list2의 각각의 원소를 더함
# 각각의 순서에 맞는 값끼리 더함 -> 1 +6, 2+7, 3+8 ...

'Programming Language > Python' 카테고리의 다른 글

[ 자주 사용되는 표준 라이브러리 ]  (0) 2023.07.16

웹 서버 프로젝트를 pm2 로 관리하던 중 생긴 문제.

 

프로젝트 요구 사항 중, 사용자가 업로드 한 유튜브 영상의 조회수를 하루마다 갱신하여 DB에 저장해야하는 기능이 필요했다.

따라서 DB에 저장된 영상의 고유 id값을 가져와 YouTube API를 통해 조회수를 수집 후, 이를 다시 DB에 Insert 하는 API를 개발 하였다.

 

key_value는 영상 id, runtime은 해당 영상의 조회수.

따로 크롤러는 만들지 않고, 해당 프로젝트는 pm2 프로세스로 관리 되던 터라 직접 cron 라이브러리를 통하여 함수를 작성하였다.

  1.  DB에 저장된 비디오의 정보를 가져와 해당 비디오의 id값만을 따로 리스트로 만들어 valuesOfContentList에 저장해준다.
  2. valuesOfContentList getVideoViewsCount 함수를 통해 유튜브에서 해당 영상의 조회수를 가져온다.
  3. 1에서 가져온 id값과, 조회수를 각각 key, value형태로 만들어 DB에 새롭게 Insert 한다.
  4. 해당 함수를 node-cron 라이브러리를 통해 주기적으로 실행할 수 있도록 설정하였다.

해당 함수를 로컬에서 테스트 후, 잘 실행되는 걸 확인하였고 이에 안심하고 배포를 하게 된다...

그러나, 예상과는 다르게 영상별로 조회수가 22시에 한번씩만 Insert 돼야 했지만 한 영상마다 수집한 조회수가 15건이 되어버렸다..

중복으로 수집된 데이터로 인해, 영상별 조회수 통계를 보여주는 페이지에서 대혼돈이 찾아왔다...(눈물)

 

왜 로컬에선 딱 한번만 수집되고, 배포 환경에선 안 되는 걸까..라는 원인 모를 상황에 약 이틀 동안 머리를 싸맸다..

처음엔 코드 문제인가 싶어 다시 찬찬히 훑어보았지만 매우 정상적으로 잘 작동 했다..

 

그러자, 불현듯 스치는 생각

https://yunja.tistory.com/12 

 

[ Jenkins + pm2 ] 트러블 슈팅

사내 프로젝트를 젠킨스를 통해 배포 자동화를 구축 후, 문제가 발생하게 된다. 상기의 이미지처럼, Jenkins Build Step을 쉘 스크립트로 비교적 간단하게 작성했다. NestJS로 프로젝트를 구성했던 터

yunja.tistory.com

해당 포스팅에서 잠깐 언급이 되었는데, 잠깐 설명하자면 배포할 프로젝트는 pm2로 관리되며 이 pm2는 프로젝트 루트 경로의 ecosystem.config.js 파일을 토대로 인스턴스 갯수를 정하여 그에 맞게 프로세스가 실행된다.

 

module.exports = {
  apps: [
    {
      name: 'app',
      script: './dist/app.js',
      instances: 15,
      exec_mode: 'cluster',
    },
  ],
};

배포 환경에선 서버 인스턴스가 15개나 되어, 위에서 작성한 영상 조회수 수집 함수가 무려 15번이나 실행되었던 것이다 !!

 

따라서 나는 한 인스턴스에서만 조회수 수집 함수가 실행 되게 하고 싶었으며, 해당 방법을 모색해보았다.

module.exports = {
  apps: [
    {
      name: 'primary',
      script: './dist/app.js',
      instances: 1,
      exec_mode: 'cluster',
    },
    {
      name: 'replica',
      script: './dist/app.js',
      instances: 13,
      exec_mode: 'cluster',
    },
  ],
};

기존에는 동일한 이름의 인스턴스로 15개를 실행하였지만, 수정한 config.js는 

하나의 인스턴스에는 primary라고 이름을 지정해주고, 다른 인스턴스에는 replica라고 이름을 명시해주었다.

이렇게 되면, 배포 서버의 노드 프로세스는 primary 이름의 프로세스 1개, replica이름의 프로세스 13개가 실행되어 진다.

 

이에 맞게, node-cron 에서 실행되어지는 getVideoViewsCount 함수에도 현재 실행되는 프로세스 인스턴스의 이름이 primary 일 경우에만, 함수를 실행하도록 하였다.

 

함수 처음 부분에 process.env.name을 가져와 primary인지 검사하여 , 맞을 경우에만 함수를 실행하도록 변경하였다.

 

해당 함수까지 변경 후 ,처음 원했던 대로 정상적으로 22시에 함수가 정상적으로 primary 인스턴스에서만 실행되었다 !!!

'Trouble Shooting' 카테고리의 다른 글

GitActions 러너 환경을 arm64 EC2로 변경하기  (0) 2023.08.04
[ Jenkins + pm2 ] 트러블 슈팅  (0) 2023.07.15

+ Recent posts