환경: (Amazon Linux 2023, Apache) public EC2  + EFS 구성


 

 

AMI 생성

EC2 내부 접속 후 아래 명령어를 통해 Apache 서버로 구성한다.

yum install httpd    # Apache 웹 서버 설치
systemctl start httpd   # Apache 서버 시작
systemctl status httpd  # 상태 확인
systemctl enable httpd  # 부팅 시 자동 시작 설정

 

http://public-ip/ 로 접속하면 기본 Apache Server 화면이 보이면 된다.

 

dnf install -y httpd amazon-efs-utils inotify-tools    # 필요한 패키지 설치
sudo mkdir -p /data    # EFS를 마운트할 디렉토리 생성
sudo mount -t efs -o tls fs-XXXX:/ /data    # EFS 마운트
df -hT | grep efs    # EFS 마운트 확인

EFS와 EC2 연결을 위해 명령어를 입력한다.

/data 경로를 생성하고 해당 폴더와 EFS를 연결했다.

 

fs-xxxx:/ /data efs _netdev,tls 0 0    # /etc/fstab에 추가

/etc/fstab 파일에 아래 명령어를 입력해 서버가 재부팅되어도 EFS가 자동으로 마운트 될 수 있게 추가한다.

 

 

sudo vi /etc/httpd/conf/httpd.conf

Apache 웹 서버는 기본적으로 /var/www/html을 DocumentRoot로 사용하지만, 이를 /data로 변경한다.

 

 

DocumentRoot "/data"
<Directory "/data">
    AllowOverride None
    Require all granted
</Directory>

파일에서 DocumentRoot를 /data로 변경하고, 아래와 같이 설정을 수정한다.

이후 /data/index.html 파일에 원하는 HTML 파일을 넣어 테스트하면 된다.

 

 

위의 설정을 완료한 후, EC2 인스턴스를 AMI로 생성한다. 생성된 AMI는 이후 Auto Scaling Group(ASG)에서 사용할 템플릿으로 활용될 예정!

 

ASG(AutoScaling Group) 생성

위에서 생성한 ami를 기반으로 시작템플릿을 구성한다.

 

#!/bin/bash
# EFS 마운트
mkdir -p /data
mount -t efs -o tls fs-xxxx:/ /data

# Apache 시작
systemctl start httpd
systemctl enable httpd

시작템플릿 구성할 때 UserData에 위의 코드를 삽입한다.

 

생성한 시작템플릿을 바탕으로 ASG 를 생성한다.

 

ASG의 인스턴스 중 하나에서 /data/index.html 파일을 수정하면, 다른 인스턴스들에도 해당 변경 사항이 반영되는 것을 확인할 수 있다. 이는 EFS에 파일을 저장하고 있기 때문에 모든 인스턴스에서 동일한 파일을 공유하고 있다는 걸 알 수 있다!


이 방식은 Apache 웹 서버를 EFS와 연동하여 파일을 자동으로 반영하는 방법을 보여준다.

CI/CD 환경을 구축하기 어려울 때, 이 방법을 활용하면 유용할 것이다.

하지만 실제 서비스 환경에서는 CI/CD 파이프라인을 구축하여 소스 코드 변경 사항을 자동으로 배포하는 시스템을 구현하는 게 제일 베스트일 것 같다!

 

▶ ALB(Application Load Balancer)의 타겟 그룹을 활용하여 Hot / Standby 구조를 구현하는 것이 목표

ASG(Auto Scaling Group)에서 관리하는 EC2 인스턴스(Hot)와 Standby 인스턴스를 운영하면서, 장애 발생 시 트래픽을 자동으로 Standby로 전환하는 걸 확인하는 테스트하고자 한다.


CloudWatch Alarm 설정

ALB의 타겟 그룹 헬스 체크 기준에 따라 특정 인스턴스가 UnHealthy 상태가 되면, UnHealthyHostCount 값이 증가하고 CloudWatch Alarm이 트리거 된다. 즉, ASG의 하나의 인스턴스라도 Unhealthy 상태면, 경보가 울린다.

 

CloudWatch Alarm의 작업 설정은 Alarm / OK 상태 모두 SNS 토픽으로 트리거되게 잡았다.

(여기서 SNS Topic 생성이 필요)

 

Lambda 생성

import boto3
import json

elb_client = boto3.client('elbv2')

def lambda_handler(event, context):
    # SNS 메시지에서 이벤트 추출
    sns_message = event['Records'][0]['Sns']['Message']
    event_detail = json.loads(sns_message)
    
    # ALB 리스너 ARN과 대상 그룹 ARN
    listener_arn = 'your_arn'
    primary_target_group_arn = 'your_arn'
    standby_target_group_arn = 'your_arn'
    
    # SNS 메시지에서 상태 추출
    state = event_detail['NewStateValue']
    
    if state == 'ALARM':
        # ALARM 상태일 때 Standby로 트래픽 분산 (50:50 or 100:0)
        action = {
            'Type': 'forward',
            'ForwardConfig': {
                'TargetGroups': [
                    {
                        'TargetGroupArn': primary_target_group_arn,
                        'Weight': 20  # 기존 대상 그룹을 일부 유지
                    },
                    {
                        'TargetGroupArn': standby_target_group_arn,
                        'Weight': 80  # 대부분의 트래픽을 Standby로 전환
                    }
                ]
            }
        }
        response_message = 'Listener rule updated: 80% traffic to standby target group'
        
    elif state == 'OK':
        # OK 상태일 때 다시 Primary Target Group으로 복원
        action = {
            'Type': 'forward',
            'ForwardConfig': {
                'TargetGroups': [
                    {
                        'TargetGroupArn': primary_target_group_arn,
                        'Weight': 100  # 다시 Primary Target Group으로 복원
                    },
                    {
                        'TargetGroupArn': standby_target_group_arn,
                        'Weight': 0  # Standby로 가는 트래픽 제거
                    }
                ]
            }
        }
        response_message = 'Listener rule updated: 100% traffic to primary target group'
    
    try:
        # ALB 리스너 규칙 업데이트
        response = elb_client.modify_listener(
            ListenerArn=listener_arn,
            DefaultActions=[action]
        )
        
        print("Response:", response)
        
        return {
            'statusCode': 200,
            'body': response_message
        }
    except Exception as e:
        print("Error:", str(e))
        return {
            'statusCode': 500,
            'body': 'Error updating listener rule'
        }

위 코드를 넣어주고 Deploy 버튼을 눌러 배포해준다.

(가중치는 원하는대로 조정하면 된다.)

 

Lambda IAM 역할은

AmazonSNSFullAccess, CloudWatchEventsFullAccess, ElasticLoadBalancingFullAccess 의 정책을 연결해준다.

( Lambda 실행 후 CloudWatch Logs에서 로그 확인 (/aws/lambda/{LambdaFunctionName} 경로) ← 해당 로그에서 에러 시 확인하기 좋다.)

 

Lambda 트리거 설정

SNS Topic을 Lambda가 구독해 트리거 설정을 해준다.

 

Hot/Standby Test

지금 설정해둔 ALB의 리스너 규칙을 보면, 아래 이미지와 같다.

 

 asg-tg의 인스턴스를 unhealthy로 만들어주면, CloudWatch Alarm이 Alarm 경보를 보낸다.

 

아래 이미지와 같이 가중치가 자동으로 변하며, standby-sg 쪽에 트래픽이 분산된다.

 

 

다시 asg-tg의 인스턴스를 healthy 상태로 만들어주면, CloudWatch Alarm 상태가 OK 상태로 바뀌고 100 / 0의 가중치로 돌아올 것이다.

 


위 방식은 나의 머릿 속에서 의식의 흐름대로 적었을 뿐... 더 좋은 방식이 있을 수 있다🙄

급증하는 트래픽에 ASG 인스턴스 늘어나는 속도가 트래픽 속도를 못 따라 잡을 때 대응 방법으로 생각해본 것!

Monitoring

데이터를 수집, 분석, 사용하여 프로그램의 목표 달성을 향한 진행 상황을 추적하고 관리 결정을 안내하는 프로세스

특정 지표를 감시하는 데 중점

 

Monitoring의 한계

Monitoring은 사전에 정의한 지표 외의 정보를 제공하지 않기 때문에 예상하지 못한 문제에 대한 원인 분석이 어렵다.

복잡한 시스템에서 발생하는 예외 상황에 대한 근본적인 이해를 돕기에는 한계가 있다.

 

Observability

로그, 메트릭, 추적과 같은 시스템에서 생성되는 데이터를 분석하여 시스템의 내부 상태를 이해하는 능력

단순한 지표 감시에 그치지 않고, 시스템의 입력과 출력 데이터를 통해 내부 동작을 추론

문제가 발생했을 때 단순히 "무엇이 잘못되었는가"를 밝히는 것을 넘어 "왜 잘못되었는가"까지 설명 가능

 

Monitoring과 Observability의 상호 보완성

Monitoring과 Observability는 상호 배타적인 개념이 아니라, 서로를 보완하여 보다 나은 시스템 안정성을 제공

Monitoring은 기본적인 상태 감시와 알림을 통해 신속한 대응을 가능하게 하고, Observability는 보다 깊이 있는 분석을 통해 문제의 근본 원인을 해결하는 데 도움을 준다.

예를 들어, 특정 시간대에 시스템 응답 속도가 느려졌다는 알림을 Monitoring을 통해 받았다면, 그 원인을 파악하는 과정에서 Observability가 필요하다. 로그와 분산 추적 데이터를 분석하여 어느 서비스에서 병목이 발생했는지, 어떤 요청이 문제를 일으켰는지를 추적할 수 있다.

 

Monitoring을 통해 기본적인 운영 지표를 관리하고, Observability를 통해 복잡한 문제의 근본 원인을 분석함으로써, 더욱 견고한 시스템을 구축

 

 

안정적이고 효율적인 시스템 운영을 위해서는 두 접근법을 적절히 통합하여 사용 ❗

'Computer Science > DevOps' 카테고리의 다른 글

[AWS] WEB - WAS 구성 (Apache - Tomcat)  (0) 2023.07.27
[Jenkins] 다양한 플러그인 활용  (0) 2023.07.24
[Jenkins] Jenkins Pipeline 구축  (0) 2023.07.03
[Jenkins] Jenkins 다뤄보기  (0) 2023.06.27
[OS] OS 모음집  (0) 2021.08.12

NAS 란

참고: https://aws.amazon.com/ko/what-is/nas/

팀원들이 네트워크를 통해 효과적으로 협업할 수 있도록
데이터를 지속적으로 사용할 수 있게 하는
파일 전용 스토리지 디바이스

  •  통신 프로토콜: IP + TCP
    • IP에 파일 데이터를 전송할 주소를 얻어 TCP가 데이터를 패킷으로 결합해 네트워크를 통해 패킷 전송
  • 파일 형식 프로토콜
    • NFS: Linux, Unix (모든 하드웨어, OS 또는 네트워크 아키텍처에서 작동)
    • SMB: Microsoft Windows
    • AFP: MacOS의 Apple 디바이스

일반적으로 생각하는 시놀로지와 같은 NAS 구성은 아래의 방법들로 구성할 수 있습니다.

 

FSx 활용

참고: https://aws.amazon.com/ko/blogs/storage/accessing-smb-file-shares-remotely-with-amazon-fsx-for-windows-file-server/

SMB Clients(사용자의 로컬 컴퓨터)에서 VPN을 통해 FSx에 접근

 

Amazon FSx for Windows File Server는 완전한 네이티브 Windows 파일 시스템이 지원하는 완전 관리형 Microsoft Windows 파일 서버를 제공합니다.

AD를 통해 사용자의 권한을 관리합니다. 이는 AWS Managed 서비스를 활용해도 좋고 기존의 Windows 환경 위에 구성된 자체 AD 서버를 활용해도 좋습니다. (단, AD 서버를 관리할 수 있어야 합니다!)

Storage Gateway는 캐시 효과를 위한 부가적인 서비스로 선택적 서비스라고 생각하시면 됩니다.

 

  • 장점
    • SMB client에서 접속하는 NAS와 제일 비슷한 환경을 제공해준다.
    • 아무래도 VPN 접속 등 내부 네트워크를 통해 접근이 가능하다.
  • 단점
    • 비싸다.
    • AD 사용을 위한 기술력이 필요하다.
    • STGW를 위한 온프렘 서버가 필요하다.

 

Workdocs 활용 (2025.04.25 지원 종료 - 생성 불가)

참고: https://blog.kyobodts.co.kr/2023/08/01/hands-on-aws-workdocs-site-%EC%99%80-drive-%EA%B5%AC%EC%84%B1%ED%95%98%EA%B8%B0/

 

 

Amazon WorkDocs는 구글 드라이브와 비슷한 AWS에서 개발한 SaaS 서비스 라고 생각하시면 됩니다.

실제 파일들을 올리면 S3에 저장되게 되고 WorkDocs는 이를 파일 스토리지 처럼 보이게 만들어 줍니다.

위의 FSx와 달리 HTTPS 통신을 하게 되어 업로드/다운로드를 할 때 인터넷 통신을 하게  됩니다.

관리자가 사용자를 생성해 전달하면 해당 사용자를 위한 저장 공간이 생기고 사용자는 해당 공간에 ID/PW로 접속하게 됩니다.

 

  • 장점
    • 사용자 당 요금으로 상대적으로 저렴하게 사용할 수 있다.
    •  사용하기 편리하다.

 

  • 단점
    • SaaS 서비스로 에러가 나면... AWS Support에 질문해야한다. (드라이브 앱 오류가 상당히 많음)
    • 사용자 관리할 관리자가 필요하다. (추후 복잡해지면, 관리하기 어려울 수도...)

AWS에서 직접 사용자를 위한 NAS 서비스가 없어서 구현에 어려움이 있다...

사실 그냥 NAS 서버 사서 쓰는게 젤 베스트 일지도...? ㅎㅎ

eksctl-control-host 생성(EC2)

보안그룹이 ssh 접속을 할 수 있게 한 뒤 EC2 인스턴스 한 대를 생성합니다. (t3.micro)

 

# kubectl
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
kubectl version --client

# eksctl
# for ARM systems, set ARCH to: `arm64`, `armv6` or `armv7`
ARCH=amd64
PLATFORM=$(uname -s)_$ARCH
curl -sLO "https://github.com/eksctl-io/eksctl/releases/latest/download/eksctl_$PLATFORM.tar.gz"

# (Optional) Verify checksum
curl -sL "https://github.com/eksctl-io/eksctl/releases/latest/download/eksctl_checksums.txt" | grep $PLATFORM | sha256sum --check
tar -xzf eksctl_$PLATFORM.tar.gz -C /tmp && rm eksctl_$PLATFORM.tar.gz
sudo mv /tmp/eksctl /usr/local/bin

eksctl version

위의 명령어를 하나씩 실행하며 kubectl, eksctl을 설치합니다.

 

mkdir .ssh
ssh-keygen
aws ec2 import-key-pair --key-name "<key-pair-name>" --public-key-material fileb://~/.ssh/id_rsa.pub

위 명령어로 worknode 생성할 때 사용할 예정!

 

EKSCTL 명령어 실행

 eksctl create cluster \
		--name <cluster-name> \
		--version 1.29 \
		--vpc-private-subnets <subnet-id>,<subnet-id> \
		--vpc-public-subnets <subnet-id>,<subnet-id> \
		--without-nodegroup



eksctl create nodegroup \
		--cluster <cluster-name> \
		--name <nodegroup-name> \
		--node-type t3.small \
		--node-volume-size=8 \
		--nodes 1 \
		--nodes-min 1 \
		--nodes-max 2 \
		--node-private-networking \
		--ssh-access \
        --ssh-public-key <key-pair-name> \
		--managed

위에 생성한 host 서버에 해당 명령어를 입력합니다.

 

cluster 생성
nodegroup 생성

위 이미지 처럼 클러스터와 노드그룹이 생성된 것을 콘솔에서 확인할 수 있습니다.

 

aws eks update-kubeconfig --name <cluster-name> --region <region>

위 명령어를 입력해 kubeconfig를 업데이트 합니다.

 

확인^^

 

혹, 콘솔 상에서 IAM 보안주체 뭐시기 에러나면, 액세스 항목을 확인하시면 됩니다.


eksctl.yaml 파일 작성

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
 
metadata:
  name: <eks-cluster-name>
  region: ap-northeast-2
  version: "1.29"
 
vpc:
  subnets:
    private:
      ap-northeast-2a: { id: <subnet-a-id> }
      ap-northeast-2c: { id: <subnet-c-id> }
  
managedNodeGroups:
  - name: <eks-nodegroup-name>
    instanceType: t3.small
    instanceName: <eks-nodegroup-ec2-name>
    volumeSize: 8
    ssh:
      allow: true
      sourceSecurityGroupIds: ["<sg-id>"]
    minSize: 1
    desiredCapacity: 1
    maxSize: 2
    labels: { type: eks-node }
    privateNetworking: true

iam:
  withOIDC: true
  
# 참고: https://eksctl.io/usage/creating-and-managing-clusters/

위 코드에 알맞은 구성을 설정한 뒤 해당 yaml파일을 s3 버킷에 업로드 합니다.

 

eksctl 배포

aws configure 설정을 위한 user와 accesskey를 생성한 뒤 설정합니다.

 

aws s3 cp <s3-uri> .

s3에 업로드한 eksctl.yaml파일을 가져옵니다.

 

eksctl create cluster -f eksctl.yaml

위 명령어를 통해 cluster를 생성합니다.

 

위 이미지와 같이 keypair 에러가 나면 ssh-keygen 명령어로 해결합니다.

 

노드그룹 까지 잘 설정된 것을 확인할 수 있습니다.

 

eksctl delete cluster -f cluster.yaml

위 명령어를 입력하면 클러스터가 삭제됩니다.

https://realyun99.tistory.com/entry/CICD-EC2-CodePipeline-Test-1

 

[CI/CD] EC2 CodePipeline Test -1

Spring project 생성 * 환경: jdk-21, Gradle, vscode 활용 vscode에서 ctrl + shift + p (Show all commands) 단축키를 누르고 spring Initializr 를 검색합니다. 원하는 대로 선택 후 프로젝트 생성하면 됩니다. 테스트 용도

realyun99.tistory.com

https://realyun99.tistory.com/entry/CICD-EC2-CodePipeline-Test-2

 

[CI/CD] EC2 CodePipeline Test -2

이전 포스팅: https://realyun99.tistory.com/entry/CICD-EC2-CodePipeline-Test-1 [CI/CD] EC2 CodePipeline Test -1 Spring project 생성 * 환경: jdk-21, Gradle, vscode 활용 vscode에서 ctrl + shift + p (Show all commands) 단축키를 누르고 sprin

realyun99.tistory.com

 

지금까지 수동 배포를 해보고 수동을 자동화 하는 방법에 대해 알아 봤습니다. 2번 포스팅에서 GithubAction을 활용했다면 이번 포스팅에선 CodeSeries를 사용해 CodePipeline 구성을 해보겠습니다.

* github, codebuild, codedeploy, codepipeline 활용


프로젝트 소스의 경우 1번 포스팅에서 진행했던 spring boot 를 활용합니다.

 

buildspec.yml 작성

version: 0.2
phases:
  install:
    runtime-versions:
      java: corretto21 # Java 21 버전
  build:
    commands:
      - echo Build Starting on `date`
      - chmod +x ./gradlew
      - ./gradlew build
  post_build:
    commands:
      - echo $(basename ./build/libs/*.jar)
      - pwd
artifacts:
  files:
    - appspec.yml
    - build/libs/*.jar
    - scripts/**
  discard-paths: yes

cache:
  paths:
    - '/root/.gradle/caches/**/*'

프로젝트 폴더의 최상단에 buildspec.yml을 넣고 위 코드를 입력합니다.

 

 

appspec.yml 작성

version: 0.0
os: linux
files:
  - source:  /
    destination: /home/ec2-user/app/
    overwrite: yes
file_exists_behavior: OVERWRITE

# ApplicationStart 단계에서 deploy.sh를 실행시키도록 합니다.
hooks: # CodeDeploy배포 단계에서 실행할 명령어를 지정합니다.
  ApplicationStart: # deploy.sh를 root권한으로 실행합니다.
    - location: deploy.sh
      timeout: 300 # 스크립트 실행 300초 이상 수행되면 실패가 됩니다.
      runas: root

프로젝트 폴더의 최상단에 appspec.yml을 넣고 위 코드를 입력합니다.

 

 

deploy.sh 작성

#!/bin/bash
#BUILD_JAR=$(ls /home/ec2-user/app/build/libs/*.jar)
BUILD_JAR=$(ls /home/ec2-user/app/*.jar)
JAR_NAME=$(basename $BUILD_JAR)
echo "> build cicd-test: $JAR_NAME" >> /home/ec2-user/app/deploy.log

echo "> build 파일 복사" >> /home/ec2-user/app/deploy.log
DEPLOY_PATH=/home/ec2-user/
cp $BUILD_JAR $DEPLOY_PATH

echo "> cicd-test.jar 교체"
CP_JAR_PATH=$DEPLOY_PATH$JAR_NAME
APPLICATION_JAR_NAME=cicd-test.jar
APPLICATION_JAR=$DEPLOY_PATH$APPLICATION_JAR_NAME

ln -Tfs $CP_JAR_PATH $APPLICATION_JAR

echo "> 현재 실행중인 애플리케이션 pid 확인" >> /home/ec2-user/app/deploy.log
CURRENT_PID=$(pgrep -f $APPLICATION_JAR_NAME)

if [ -z $CURRENT_PID ]
then
  echo "> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다." >> /home/ec2-user/app/deploy.log
else
  echo "> kill -15 $CURRENT_PID"
  kill -15 $CURRENT_PID
  sleep 5
fi

echo "> $APPLICATION_JAR 배포" >> /home/ec2-user/app/deploy.log
nohup java -jar $APPLICATION_JAR >> /home/ec2-user/deploy.log 2>/home/ec2-user/app/deploy_err.log &

프로젝트에 scripts 폴더를 생성하고 위 코드를 넣어줍니다.

 

 

CodePipeline 구성

나머진 디폴트로 설정합니다. (아티팩트도 기본 설정으로)

 

소스는 GitHub을 연결해줍니다. 

 

소스 트리거는 본인이 push하는 브랜치로 설정합니다.

 

빌드는 CodeBuild를 선택하고 프로젝트를 생성합니다.

 

새 창이 열리며 위와 같이 build 프로젝트 생성을 시작합니다.

 

설정이 끝났으면 CodePipeline으로 계속 버튼을 누르고 이어 진행합니다.

 

다음 버튼 클릭해 배포 단계 구성 합니다.

 

CodeDeploy의 경우 2번 포스팅에서 생성했던 애플리케이션에 새로운 배포 그룹을 만들어 진행합니다.

프로젝트를 배포할 인스턴스 이름 태그를 걸어줍니다.

로드밸런싱 비활성화를 누른 뒤 배포 그룹 생성합니다.

 

2번 포스팅에서 생성했던 애플리케이션과 위에서 생성했던 배포 그룹을 선택합니다.

검토 후 파이프라인을 생성합니다.

 

 

❗ codedeloy agent 에러 확인 ❗

 cd /var/log/aws/codedeploy-agent
  cat codedeploy-agent.log

 

 

appspec.yml 파일에 아래 코드 추가

file_exists_behavior: OVERWRITE

 

❗ SNAPSHOT-plain.jar 와 SNAPSHOT.jar 중 SNAPSHOT.jar만 실행 가능한 아카이브 ❗

SNAPSHOT-plain.jar를 생성하지 못하게 아래 코드를 build.gradle에 추가해준다.

  jar {
      enabled = false
  }

'Toy Project' 카테고리의 다른 글

[CI/CD] EC2 CodePipeline Test -2  (1) 2024.03.19
[CI/CD] EC2 CodePipeline Test -1  (0) 2024.03.18

+ Recent posts