안녕하세요 이번 글에서는 평소 AWS 내의 리소스를 관리하며 , 대충 이해만 하고 넘어갔던 접근제어에 대해서 알아보겠습니다.

우선, 저 같은 경우는 그저 특정 리소스를 요청할 때마다, 필요한 권한을 포함한 해당 리소스의 모든 권한을 전부 허용해 주고  사용하곤 했었습니다. 

 

또한, 해당 역할에 대한 이해와 동작 과정에 대한 이해 없이 무작정 권한을 할당해 주고, 사용하다 보니 자주 Permission denied라는 문구를 보곤 했었습니다.

 

따라서 AWS의 접근 제어 정책에 대한 정확한 이해가 필요해 해당 과정을 글로 녹여내게 되었습니다.

 

들어가며..

AWS 클라우드를 사용하는 몇 가지 방법이 있습니다.

 

웹 콘솔에서 조작하는 방법, AWS CLI로 사용하는 방법, AWS에서 제공하는 SDK를 이용해 여러 프로그래밍 언어로 API 호출하는 방법이 있죠. 

 

첫 번째 방법을 제외한 CLI와 SDK를 사용할 경우, 매 API 호출마다 처리되는 인증 절차가 있습니다.

 

여기서 , IAM 사용자를 통해 발급받은 ACCESS KEYSECRET KEY( Secret Access Key)를 사용하게 되죠.

 

ACCESS KEY
- AWS 계정 또는 IAM 사용자를 식별하는 데 사용되는 고유한 식별자 입니다.
- 요청 주제 (Principal)을 인증합니다.

SECRET KEY
- HMAC ( Hash-based Message Authentication Code ) 서명값을 검증합니다.
- 요청의 특정 부분을 해싱하여 디지털 서명을 생성하고, 이 서명을 요청에 포함시켜 요청을 보내는 과정을 포함합니다.
- 이렇게 함으로써, 보내는 측이 요청을 생성한 적절한 사용자임을 인증합니다

 

AWS IAM ?

IAM은 AWS 전체의 권한 통제 시스템을 칭하는 말이며, 

I ( Identity) : AWS로 요청을 할 수 있는 보안주체 (Principal)를 의미합니다.

AM ( Access Management) : 누가 어떤 리소스들에 대해 어떤 일을 할 수 있는 권한을 가지는지를 의미합니다.

 

IAM 보안주체 ( Principal) 는 여러 사용자 유형이 있는데, Cloud Trail 서비스에서 다음과 같이 구분이 됩니다.

  • Root - API 요청이 AWS Account 자격 증명을 사용 ( 처음 계정 생성시 만들어지는 슈퍼 유저 )
    - 모든 리소스에 대한 접근 권한이 있기 때문에 사용 안하는 것을 적극 권장
  • IAM User - API 요청이 IAM User의 자격 증명을 사용 ( 장기 credential을 사용 )
  • Assumed Role - API 요청이 AWS STS(Simple Token Service) AssumeRole을 통해 획득한 임시 보안 자격 증명을 사용
  • AWS Account - 다른 AWS Account에서 요청
  • AWS Service - AWS 서비스에 속한 AWS 계정을 통해 요청

위와 같은 형식으로 Cloud Trail 서비스 내에서 유저의 유형과, 유저의 이름, 해당 유저가 어떤 행동을 했는지 기록되게 됩니다.

해당 JSON을 이용하여, 보안 감사 및 추적의 목적으로 사용할 수 있겠죠 ?

 

IAM의 인증과 인가

AWS IAM은 권한 제어를 위한 인증과 인가를 둘 다 담당합니다.

 

우선 인증으로는 , 보안 주체 ( principal )가 갖게되는 credential은 장기 ( Long-term) 와, 임시 자격 증명이 있습니다.

 

대표적인 장기 credentials 로는 IAM User(사용자)의 자격 증명을 그대로 이용한 credential입니다. 

( IAM User 생성시, 위에서 언급한 Access key와 Secret Key를 발급 받을 수 있습니다. )

 

반면에 임시 자격 증명으로는 IAM Role의 인증이 있습니다. 

예를 들어, 특정 EC2 instance가 다른 AWS 리소스에 접근할 때 사용하는 역할등이 있겠죠.

( 이 과정에서도 특정 리소스를 호출 하는 과정이기에 인증과정이 이루어집니다. )

 

대신, IAM User의 자격증명을 그대로 이용하는 credential과는 달리, 해당 임시 자격 증명의 시간을 제한할 수 있습니다.

 

일련의 인증 과정을 거친후, 해당 인증된 사용자(또는 서비스)가 요청에 대한 정책이 허용이 된다면, 그제서야 API 호출은 성공하게 됩니다.

 

예시 )

- A라는 IAM User는 특정 리소스를 호출합니다. ( S3 Object Put, Get 등 )

- 해당 요청은 IAM 서비스를 통해 보안 주체(요청자)가 적절한 사용자인지 판단합니다(인증). 

- 그 후, 인증된 사용자라면 해당 요청자의 권한을 확인합니다. 

- 만약 정책에 맞는 요청을 했다면(인가) 요청을 받은 해당 리소스는 요청에 대한 응답을 합니다.

 

 

IAM Policy

 

위의 예시에서 "정책"을 언급했는데, 이 정책은 말 그대로 보안 주체가 가지고 있는 권한을 뜻합니다.

 

해당 정책의 구조는 대략 다음과 같습니다.

 

{
	# 예시
	"Version": "2012-10-17",
	"Statement": [
		{
        	# 허용 할건지, 거부 할 것 인지
			"Effect": "Allow or Deny",
            
            # 어떤 행동을 ?
			"Action": [
            	"s3:Get",
                "s3:Put"
            ],
            
            # 어떤 리소스들에 대해 ?
			"Resource": [
            	"arn:aws:s3:<BUCKET-NAME>:<OBJECT-PATH>"
            ]
		}
	]
}

 

이에 추가로, Condition 블록을 통해 조건을 걸어줄 수 도 있습니다. 위 코드 블럭에 추가하자면, 

srcIP가 특정 대역에 속하는 경우에만 허용 같은것 말이죠.

 

어떤가요? 어느정도 이해가 되셨나요 ? 한 가지 더 재밌는 사실이 남았습니다. 

 

바로 위에서 본 Policy JSON는 Identity Based(보안 주체 기반) Policy 이고, AWS 에서는 위와 유사하지만 

 

Resource Based Policy( 리소스 기반 정책) 으로 종류가 나누어집니다.

 

위의 JSON 의 Statement 블록안에, Principal 블록이 추가 됩니다. 

 

쉽게 설명하자면, 특정 리소스 ( 예 : S3) 에 특정 보안 주체 (principal)만 허용된 Actions를 수행 할 수 있다 입니다.

 

예를 들어보시죠 . IAM 사용자에게 역할을 연결하는 것과는 조금 다릅니다.

  • S3 리소스를 생성합니다.
  • 생성 후, 해당 S3에 접근할 수 있는 주체를 특정합니다. ( 예 : a라는 iam user의 ARN -> arn:aws:iam:<ACCOUNT_ID>:a_user)
  • 해당 주체는 Put과 Get 호출을 할 수 있습니다.
  • 그렇지만 특정 Path 에만 접근이 가능합니다. 또는 모든 Path에 접근이 가능합니다.
  • 또한 , 특정 ip로만 접근할 경우에만 가능합니다. ( 예 : 회사 사무실의 공인 라우터 또는 게이트웨이 IP ) 

이를 JSON 형식으로 표현하면 다음과 같아집니다.

 

{
	# 예시
	"Version": "2012-10-17",
	"Statement": [
		{
			"Effect": "Allow",
            
            "Principal" : {
            	"AWS" : [
                	"arn:aws:iam:<ACCOUNT_ID>:a_user"
                ]
            }
        
            # 어떤 행동을 ?
			"Action": [
            	"s3:Get",
                "s3:Put"
            ],
            
            # 어떤 리소스들에 대해 ?
			"Resource": [
            	"arn:aws:s3:<BUCKET-NAME>/<OBJECT-PATH>*"
            ],
            
            "Condition" : {
            	"IpAddress": {
                	"aws:SourceIp" : "1.1.1.1"
                }
            }
		}
	]
}

 

 

 그렇다면 여기서 궁금증이 생기실 수도 있을텐데요,

 

" 어라 ? 그럼 IAM 사용자가 리소스를 호출하려 할 때, 리소스 정책과, 유저 정책을 둘 다 만들어줘야 하나 ? , 둘 중에 하나만 열어줘도 되던데 ? "

저도 같은 생각을 했습니다만, 이는 반은 맞고 반은 틀렸습니다.

 

바로 같은 계정인지, 아니면 다른 계정인지에 따라 이는 구분이 되어 적용됩니다.

 

쉽게 설명하자면, 같은 Acount_id를 가지고 있는 IAM 사용자와 리소스일 경우에는 둘 중에 하나만 허용해줘도 API 호출이 가능합니다.

 

다만, 다른 Account_id를 가지고 있는 IAM 사용자와 리소스일 경우에는 , 둘 다 허용해줘야 API 호출이 가능해집니다.

 

즉, IAM 사용자는 해당 리소스에 대한 적절한 권한을 가지고 있어야 하고, 해당 리소스는 특정 IAM 사용자 ( 다른 계정의)의 ARN을 보안 주체 (Principal 블록) 에 작성해줘야 정상적인 호출이 가능해집니다.

 

또한, 사용자의 Effect는 특정 Actions가 Allow가 돼있고, 리소스에선 보안주체로 사용자가 명시 돼있어도 리소스 정책에서 Effect가 Deny가 돼있다면, AWS 정책상 Deny의 우선순위가 높기 때문에 사용자의 호출은 거부 됩니다.

 

 

마치며 ..

여기까지 IAM에 대한 기본적인 내용을 예시와 함께 간단하게 살펴보았습니다. 

 

다음번엔 AWS 임시 자격증명의 유형과 사용방법에 대해 좀 더 알아보겠습니다. 

 

긴 글 읽어주셔서 감사합니다.

 

 

 

 

 

'AWS' 카테고리의 다른 글

AWS Cloud Support Associate 1차 면접 후기  (7) 2023.12.12
AWS ECS 오토 스케일링 및 배포 자동화  (0) 2023.08.01

안녕하세요 ! 오늘은 제목 그대로 AWS Cloud Support Associate 1차 면접 후기에 대해 말씀드리려고 합니다 !

 

 

평범한 일상을 보내고 있던 중 링크드인을 통해 AWS Cloud Support 리쿠르터 분께 면접 제의가 오게 됐습니다. (ㄷㄷㄷ)

처음엔 거짓말인줄 알았어요
직무 설명

정말 감사하게도 제 링크드인 이력서를 보시고 연락을 주셨고, 사진에서 보시다싶이 서류전형은 자동으로 통과 된다고 말씀해주셨습니다.

 

이후, 평소에 잘 업데이트 하고 있던 이력서를 냉큼 제출하였고, 제출한지 거의 10분만에 서류전형 이후의 프로세스를 말씀해주셨습니다.

 

서류전형을 통과 이후에는 Online Assesment 를 보게되었습니다.

 

해당 테스트는 조금 특별했던게 실제 CSE ( Cloud Support Engineer)분들이 업무를 하시면서 실제 겪으시는 사례를 기반으로 질문이 나왔으며, 평가내용은 기본적인 기술지식, Troubleshooting, 제 업무방식에 대한 질문들이 있었습니다 .

 

또한 Networking 부분의 꽤나 Deep dive한 질문도 많았습니다.

 

온라인 테스트는 2일 이내 제출해달라고 하셨지만, 2시간? 만에 해치워버렸습니다.

 

온라인 테스트를 통과 후, 1차 인터뷰 일정을 잡게 되었습니다

 

특이하게도 1차 인터뷰 진행 전, 담당 리쿠르터 분께서 지원직무 변경(Networking -> Deployment) 을 제안하셨고, 평소에 DevOps에 관심이 많았던 터라 흔쾌히 수락하고 면접 준비를 하였습니다. 면접 준비 기간은 약 3일 정도 되었던 것 같습니다(감기 몸살때문에 ..)

 

리쿠르터분께서 친절하시게도 1차 인터뷰 진행 시, 기술 인터뷰이기에 지원 직무 기반 지식에 대해 참고할만한 문서를 보내주셨습니다.

 

해당 문서를 보며 평소에 두루뭉실하게 알고 있던 Network, OS, Docker 등을 정리하고

(이 과정에서 참고한 유튜브 채널로는 널널한 개발자님과 쉬운코드님, 우테크 영상을 주로 봤습니다) 

 

그 유명한 아마존 리더십 원칙과 STAR 형식(Situation, Task, Action, Result)의 답변을 염두하며 면접을 준비했습니다

 

이후, 면접당일이 되었고 면접 보는 회사 규모가 규모라 그런지 엄청 긴장하며 화상면접을 보게 되었습니다 하하;;

 

인터뷰를 진행하며 deep한 질문에 대답을 잘 못해서 버벅거렸는데, 면접관분께서 생각나면 천천히 말씀해달라고 친절하게 말씀해주셨습니다.. ㅠㅠㅠ 

 

그렇게 1시간이 후다닥 지나가게 되고, 마지막엔 면접관분께 궁금한 것을 질문하는 시간에 개인적으로 몇가지 궁금한걸 질문드렸습니다.

 

결과가 어떻게 나올진 모르겠지만 
세계적인 기업인 AWS Cloud Support Engineer 현직자 분과 대화를 주고 받을 수 있었던 것만으로도 정말 값진 경험이었습니다.

 

제 경험과 공부했던 내용을 다시 정리하기에도 너무 좋았구요.

 

마지막으로 제가 받았던 기술 면접 질문을 기억나는대로 정리했습니다(정리한 것 보다 훨씬 많았어요 ..ㅠ )

아무쪼록 다른 개발자분들이나 엔지니어 분들에게 도움이 되었으면 좋겠습니다 !! 긴글 읽어주셔서 감사합니다 !!

 

  • 간단한 자기소개
  • TCP/IP에 대해 설명 해주세요
  • HTTP Method의 종류와 역할?
  • HTTP 응답코드에 대해 아는대로 설명해주세요
  • TCP와 UDP의 차이를 설명해주세요.
  • 3-way handshake에 대해 설명해주세요
    • 4-way handshake에 대해 설명해주세요
    • 4-way handshake는 왜 4-way인가요 ?
  • TLS/SSL에 대해 설명해주세요
    • TLS handshake에 대해 설명해주세요
  • 서버A에서 서버B로 ping 요청을 보낸다고 가정 시, 서버B에서 해당 요청을 차단하는 방법 ?
    • 동일 네트워크 대역일 경우
    • 다른 네트워크 대역일 경우
    • 운영체제 수준에선 어떻게 할까요 ?
  • 서버에서 만약 503 Error가 떨어졌을때, 어떤 조치를 취하실건지 설명해주세요
  • www.google.com에 접속을 했습니다. 요청을 보내고 받기까지의 과정을 자세히 설명해주세요.
  • OOM이 발생하는 근본 원인은 무엇일까요 ?
    • 만약 발생했을때 조치는 어떻게 하실건가요 ?
  • 리눅스에서 chmod 600 testFile을 했을때 어떤일이 생기나요 ?
  • 리눅스 iptables에 대해 설명해주세요
    • ACCEPT, DROP, REJECT 은 어떻게 사용되는지 말씀해주세요
  • ssh의 원리가 어떻게 되나요 ?
    • 명령어 구성은 어떻게 되나요 ?
  • 도커 컨테이너는 어떻게 독립된 환경을 가지게 되나요 ?
    • 커널의 어떤 모듈을 사용하는지 말씀해주세요
  • 도커 컨테이너 네트워크 종류와 각각의 차이에 대해 말씀해주세요

 

 

 

 

 

 

 

 

'AWS' 카테고리의 다른 글

IAM  (0) 2024.03.30
AWS ECS 오토 스케일링 및 배포 자동화  (0) 2023.08.01

안녕하세요 이번 포스팅에서는 수십 개의 크롤러들을 도커라이징하며 겪었던 문제 및 문제 해결을 했던 과정을 소개 시켜 드리려고 합니다.

( 그 전부터 정리해야지.. 정리해야지.. 하고 미루다가 이제서야 하게 됐네요)

그전에 왜 수십 개의 크롤러들을 도커라이징 고려를 했는지에 대해 말씀드리려 합니다.

 

우선, 기존 사내 서버실 데스크탑에서 주기적으로 실행되는 크롤러 프로세스들은

- 로컬 개발 환경과 배포 서버 환경의 차이로 인한 각종 에러로 인해, 해당 에러를 처리하는 데에만 꽤나 유의미한 시간이 들었습니다.

- 프로세스가 다운 될 정도의 에러가 발생 시 즉각적으로 대응하지 못하는 점.
(해당 상황 발생 시, 대부분은 프로세스를 다시 실행 시키는 정도의 수준으로 대응이 가능했습니다.)

- 크롤러 내부 로직 수정 시, 수동 배포와 같은 반복적인 작업.

위 사항을 비롯해 기존부터 크롤러 관리의 어려움을 느끼고 있어서 해당 부분을 팀원분들과 논의를 마친 후, 도커라이징을 하기로 결정하게 되었습니다.

 

크롤러 "공통" Dockerfile의 대략적인 내용은 아래와 같습니다

# Base image
FROM python:3.9

# Set the working directory in the container
WORKDIR /app

RUN apt-get update && apt-get install -y \
    chromium \
    chromium-driver

# Copy the project files to the working directory
COPY . .

# Install required libraries
RUN pip install -r requirements.txt

# Set the entrypoint command
ENTRYPOINT ["/bin/bash", "-c", "exec \"$@\"", "--"]

 

ENTRYPOINT만 작성한 이유는 해당 도커 이미지의 진입점을 /bin/bash로 설정해놓은 다음

  • 뒤에 오는 CMD 명령을 인자로 받는 다는 뜻이며 , 이는 곧 컨테이너 별로 다르게 실행될 스크립트를 지정해줌으로써 컨테이너 별로 공통 이미지를 가지며 서로 다르게 실행시키기 위함입니다.
  • 컨테이너가 실행될때의 명령을 컨테이너마다 각기 달리 줘야 하기 때문입니다.

 

크롤러 별 도커 컴포즈 구성은 대략적인 내용은 아래와 같습니다.

 

// 도커 컴포즈의 버전을 명시
version: '2'

services:
  crawler1:
    container_name: crawler1
    build: .
    command: python3 -u main.py crawler1
    network_mode: host
  craler2:
    container_name: crawler2
    build: .
    command: python3 -u main.py crawler2
    network_mode: host
  
  ...​

 

 

각 서비스의 컨테이너 별 command는 단일 도커 파일에서 봤을때 다음과 같은 효과가 생기게 됩니다.

  • ENTRYPOINT ["/bin/bash", "-c", "exec \\"$@\\"", "--"]
  • CMD [”python3”, "-u", "main.py", “crawler1”]
더보기

여기서 한가지 궁금점이 생기실 수도 있는데 만약, ENTRYPOINT만 정의되어 있고, 도커 컴포즈 실행시 Command 지시자가 없다면 어떻게 될까?

해당 물음에 대한 자세한 포스팅은 아래 링크를 참조하시면 좋을 것 같습니다.

https://www.popit.kr/%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EC%B2%98%EC%9D%8C-docker-%EC%A0%91%ED%95%A0%EB%95%8C-%EC%98%A4%EB%8A%94-%EB%A9%98%EB%B6%95-%EB%AA%87%EA%B0%80%EC%A7%80/

 

요약하자면, 도커 컨테이너는 가상머신과 같이 하나의 온전한 서버를 제공하는 것이 아닌, 명령을 실행하는 환경만 제공하고, 그 명령을 실행할 뿐입니다. ( 이 이야기에 대해선 다음번에 좀 더 자세히 다루도록 하겠습니다. )

 

AWS EC2 환경에서의 컨테이너

  • 크롤러 EC2 인스턴스(우분투 리눅스)에는 도커만 설치하고, 크롤러 이미지는 따로 도커 허브를 이용하진 않았습니다.
    (테스트 목적이기도 했고, 퍼블릭한 공간에 이미지를 노출하기가 꺼렸습니다. + 프라이빗은 유료..)
  • 버전 관리를 위해 git 으로 필요한 소스코드 파일과 도커 파일, 도커 컴포즈 파일만 호스트에서 이미지를 빌드하고 컨테이너를 실행하게 했습니다.
  • 성공적으로 첫 단계를 밟았다고 생각했으나 문제가 생기게 됩니다.

DB ETIMEDOUT 문제

  • EC2 인스턴스 내의 프로젝트 폴더에서 직접 코드를 실행했을 때는 잘 되었지만
  • 도커 컴포즈로 컨테이너를 실행시켰을 때, 로그에 DB ETIMEDOUT이라는 에러가 발생하여, 컨테이너가 종료되는 에러가 발생했습니다.
  • 당시의 문제점 파악으로는, 우선 네트워크 문제를 고려했습니다. ( 해당 문제가 아닐 수도 있습니다. )
  • 정리하자면, EC2 인스턴스에서의 크롤러 프로세스는 호스트 네트워크를 사용해 DB와 연결을 하고 데이터를 받아와 크롤링을 합니다.
  • 하지만, 컨테이너는 도커가 부여해준 가상 인터페이스를 가지고 외부로 나가 DB와 연결을 하기에 모종의 이유로 타임아웃이 발생할거라 생각했습니다.
더보기

💡 Docker 네트워크는 bridge, host, overlay 등 목적에 따라 다양한 종류의 네트워크 드라이버(driver)를 지원하는데요.

  • bridge 네트워크는 하나의 호스트 컴퓨터 내에서 여러 컨테이너들이 서로 소통할 수 있도록 해줍니다.
  • host 네트워크는 컨터이너를 호스트 컴퓨터와 동일한 네트워크에서 컨테이너를 돌리기 위해서 사용됩니다.
  • overlay 네트워크는 여러 호스트에 분산되어 돌아가는 컨테이너들 간에 네트워킹을 위해서 사용됩니다.

따라서, EC2 인스턴스의 네트워크 인터페이스를 그대로 활용하기 위해 위의 도커 컴포즈 파일의 네트워크 모드를 호스트로 설정해주었습니다.

 

컨테이너 헬스 체크 과정

  • 호스트 네트워크 모드를 사용해 성공적으로 크롤러들을 실행시키는데 성공했습니다.
  • 그러나, 크롤러가 작동할수록 서버의 CPU 사용량(4vCPU, 8GB)이 무지막지하게 늘어났고(아마 브라우저를 계속 생성해서 그런 거 같음)
    • 추후에 생각난건데, 네트워크 I/O 부분도 고려하지 못한게 아쉽다고 생각이 드네요
  • 그 후엔 컨테이너는 정상 실행 상태지만 내부에서는 파이썬 프로세스가 exit가 되어버리며 크롤링이 되지 않는 상황이 발생했습니다.
  • 따라서 적절한 헬스체크 방식이 필요했으며 헬스체크 실패 시 , 컨테이너를 다시 띄우려고 시도 했습니다.

CloudWatch

EC2 서버에 AWS에서 제공하는 CloudWatch를 사용하여 CPU 사용량을 주기적으로 모니터링 했습니다.

  • 크롤러 컨테이너들을 실행하고 CPU 사용량을 관측한 결과, 평균 CPU 사용률이 7~80%를 왔다갔다 했었습니다.
  • 이후로, 파이썬 프로세스가 컨테이너 내부에서 종료 됐을 시에는 CPU 사용률이 2~30%를 왔다갔다 했었습니다.
  • 그래서 위 지표를 토대로 CloudWatch의 CPU 사용률이 30%보다 아래일 때, 크롤러 컨테이너들이 종료됐다고 판단하고,
    이것을 트리거 삼아 AWS SNS에 이벤트를 게시하게 하였습니다.

SimpleNotificationService ( SNS )

AWS SNS는 말그대로 알림을 전송해주는 서비스이며, 위 사진과 같이 여러 엔드포인트에 대하여 알림을 제공할 수 있다.

  • 위 서비스의 새로운 주제를 생성해, 엔드 포인트를 람다 함수로 설정하여, (나름의) 헬스 체크 실패 시 다시 컨테이너를 띄울 수 있는 방식을 택했습니다.

AWS Lambda

Layers 구성

  • 처음 람다 함수를 실행할 때, 필요한 모듈을 import를 했어야 했는데
  • docker, slack, 인스턴스에 명령을 전달하기 위해 인스턴스와 연결하는 boto3 모듈 가 import가 되지 않았습니다.
  • 람다 공식 문서를 잘 살펴보니 함수를 실행하기 위한 특정 모듈들은 직접 .zip파일로 만들어서 업로드를 해줘야 동작을 할 수 있었습니다.
    • 그래서 필요한 모듈들을 직접 pip3를 이용해 설치하고,
    • 모듈들이 설치된 파일을 .zip파일로 압축시켜 신규 Layers를 생성한 뒤, 해당 Layer를 추가 해주었습니다.

Lambda 코드

import boto3
import os
import docker
import time
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError

# ssm-client 계정 엑세스 키
access_key = os.environ['access_key']
secret_key = os.environ['secret_key']
region = os.environ['region']
instance_id = os.environ['instance_id']
slack_token = os.environ['slack_token']
slack_channel = os.environ['slack_channel']
container_list = os.environ['container_list']
remove_command = os.environ['remove_command']
compose_up_command = os.environ['compose_up_command']


ec2_client = boto3.client(
       "ec2",
       aws_access_key_id=access_key,
       aws_secret_access_key=secret_key,
       region_name=region
   )

# ssm_client를 사용하기 위해선 해당 인스턴스에 ssm_agent가 설치 되어 있어야 하며,
# 해당 access_key를 사용자의 권한에 위의 사진과 같은 권한을 설정해주었다.
ssm_client = boto3.client(
       "ssm"
       aws_access_key_id=access_key,
       aws_secret_access_key=secret_key,
       region_name=region
   )

# 도커 데몬의 기본 포트인 2375를 이용해 람다에서 명령을 실행해주기 위함
res = ec2_client.describe_instances(InstanceIds=[instance_id])
docker_host = "tcp://" + res["Reservations"][0]["Instances"][0]["PublicIpAddress"] + ":2375"

docker_client = docker.DockerClient(base_url=docker_host)
slack_client = WebClient(token=slack_token)


# 실행될 메인 람다 함수 이며 , 필수 파라미터로 event와 context가 있다.
# 이게 왜 필수냐면 트리거가 발동 되거나, 어떠한 이벤트 발생 후, 람다에 전달할때 람다에서 해당 event를 매개변수로 받아 사용해야 하기 때문.
# 근데 여기선 트리거 발생시, 실행중인 컨테이너를 전부 삭제하고 새롭게 띄우는 방식으로 설계 했기 때문에 매개변수는 따로 사용하지 않았다.
def lambda_handler(event,context):
    containers = docker_client.containers.list()

    send_slack_message('크롤러 중지 감ji')
            
    message2 = '크롤러 재실행'
    send_slack_message(message2)
            
    remove_containers()
    docker_compose_up()
            
    message3 = '크롤러 재실행 complete'
    send_slack_message(message3)
                
    return

def remove_containers():
    ssm_client_command(remove_command)
    
def docker_compose_up():    
    # docker-compose up 실행
    project_dir = '/home/ec2-user/Overware_crawler'
    compose_file = [f'{project_dir}/docker-compose.yml']
    project_name = 'my_project'

    options = {'--project-directory': project_dir, '--project-name': project_name}
    docker_client.api.compose.up(
        compose_file=compose_file,
        detach=True,
        options=options,
        timeout=60,
    )

def ssm_client_command(command):
    response = ssm_client.send_command(
        InstanceIds=[ec2_instance],
        DocumentName="AWS-RunShellScript",
        Parameters={
            "commands": [command],
        }
    )
    return

def send_slack_message(message):
    try:
        slack_client.chat_postMessage(channel=slack_channel, text=message)
    except SlackApiError as e:
        slack_client.chat_postMessage(channel=slack_channel, text=str(e))

 

여기까지 위에 열거한 문제 (컨테이너는 정상 실행 상태지만 내부에서는 파이썬 프로세스가 exit) 를 해결 하기 위해 했던 과정들입니다.

 

예전에 제가 개인적으로 따로 과정을 정리 해두었지만 글이 너무 중구난방한 상태였고, 해당 문제를 다시 복기하며 보니 개선점이 꽤나 보이네요 ..ㅎㅎㅎ;;

 

과정들을 겪고나니 이제서야 도커 스웜, 쿠버네티스 같은 컨테이너 오케스트레이션을 이유가 좀 더 와닿는 것 같습니다.

 

아쉬운 점은,

  1. 하나의 컨테이너가 이상이 발생해도, 정상 실행중인 컨테이너 모두를 내리고 다시 실행해야 하는 점
  2. 컨테이너의 내부 프로세스 상태를 확인 할 수 있는 방법을 모색하지 못한 점
  3. 왜 호스트의 CPU 사용률이 8~90%까지 되었는지 정확한 원인 파악을 못한 점 ( 추정 정도만 ..)
  4. 좀 더 Best Practice에 다가가지 못한 점

등이 있겠네요..ㅠㅠ

 

긴 글 읽어주셔서 감사합니다.

 

 

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

좌충우돌 서비스 모니터링 핀포인트 도입기  (0) 2023.07.25

서론

이번 글에서는 클라이언트-서버 간 통신을 하기 전 , 서로의 상태를 확인하는 과정인 3-way hadnshake에 대해 알아보고, 

실제 인터페이스의 패킷을 캡쳐해 해당 과정을 확인해보겠습니다.

 

먼저, TCP/IP 프로토콜 스택에서의 4계층은 TCP와 UDP가 담당하고 있습니다.

여기서 4계층의 목적은 목적지를 찾아가는 주소가 아니라 
(이 과정은 이미 2계층-MAC주소과 3계층-IP주소에서 이루어집니다)
 애플리케이션에서 사용하는 프로세스를 정확히 찾아가고 데이터를 분할한 패킷을 잘 분해하고 조립하는 것입니다.

 

TCP 프로토콜은 정보유실 없는 통신을 보장하기 위해 패킷에 번호(Sequence Number)를 부여하고, 잘 전송되었는지에 대해 응답(Acknowledge Number) 합니다. 

 

또한, 한꺼번에 얼마나 보내야 수신자가 잘 받아 처리할 수 있는지 전송 크기(Window Size)까지 고려해 통신합니다.

( TCP/IP 송수신에 대해선 https://yunja.tistory.com/28에 다루었습니다. )

 

본론

TCP에서는 위에서 설명했듯이 유실없는 안전한 통신을 위해 통신 시작 전, 사전 연결작업을 진행합니다.

 

패킷에 순서를 부여하는 것을 Sequence Number, 응답 번호를 부여하는 것을 ACK 번호라고 부른다고 했는데요. 두 번호가 상호작용해 순서가 바뀌거나 중간에 패킷이 손실된 것을 파악할 수 있습니다. 대략적인 과정은 아래와 같습니다.

 

위의 과정으로 HTTP 요청 시, 서버 측의 응답하려는 데이터를 패킷 단위로 쪼개어 보내게 됩니다.

  1. 출발지 송신 측에서 시퀀스 번호를 0으로 보냅니다.
  2. 수신 측에서는 송신측의 0번 패킷을 잘 받았다는 표시로 응답 번호(ACK)에 1을 적어 응답합니다.
  3. 수신 측의 패킷을 받은 송신 측은 시퀀스 번호를 1로 , ACK 번호는 상대방의 0번 시퀀스를 잘 받았다는 의미로 시퀀스 번호를 다시 1로 부여해 송신 합니다.

위의 과정대로 TCP에서의 Sequence Number(Seq), 응답번호 (ACK)가 상호작용하여 데이터를 주고 받는 것을 살펴보았는데요.

 

이것을 토대로, 오늘의 주제인 위 과정이 실행되기 전의 사전 연결작업인 3방향 핸드셰이크에 대해 알아보겠습니다.

패킷 네트워크에서는 동시에 많은 상대방과 통신하므로 정확한 통신을 위해서 각 통신에 필요한 리소스를 미리 확보하는 작업이 중요합니다.

TCP에서는 3번의 패킷을 주고받음으로써 통신을 서로 준비(송,수신 측이 현재 패킷을 주고 받을 수 있는 상태인지)하기 때문에 3방향 핸드셰이크라고 부릅니다.

 

 

  1. 통신 시도시 송신자는 플래그에 있는 SYN 필드를 1로 표기해 패킷을 보냄
    • 이때 자신이 사용할 첫 seq no(시퀀스 번호)를 적어 보냄.
  2. SYN 패킷을 받은 수신자는 SYN과 ACK 비트를 플래그에 1로 표기해 응답함.
    • 첫 패킷이므로 SYN을 1로 표기하고, 송신자가 보냈던 패킷의 응답이기도 하니, ACK 도 함께 1로 써서 보냄
  3. ACK 번호는 10번까지 잘 받았으니 다음에는 10+1번을 달라는 의미임.
  4. 수신자의 응답을 받은 송신자는 연결 확립을 위해 다시 한번 응답 메시지를 보냄.
    • 수신자가 ACK 번호를 11로 표기했으니, seq no 를 11로 표기해 응답함.
    • 동시에 수신자의 시퀀스 번호 20에대한 응답이니, ACK번호를 2로 보냄(20번까지 잘 받았음, 다음꺼 보내줘)
    • ACK 번호를 비교해가며, 패킷 유실이 있으면 파악 후, 메모리에 유지해놓은 데이터를 이용해 재전송함.

대략적인 과정을 통해 3방향 핸드 셰이크 패킷이 어떻게 주고 받는지 알아봤으니, 직접 패킷을 캡쳐해 확인해보도록 하겠습니다.

curl www.google.com을 통해 GET 요청을 보낸 후, 캡쳐한 패킷 (*와이어샤크)

 

$  sudo tcpdump -i en0 -w /Users/yunsmac/Desktop/google.pcap tcp port 80

위 명령은 현재 퍼블릭 네트워크로 통신 중인 제 MAC 주소를 가진 인터페이스에 대해, tcp프로토콜 port 80인 패킷만 캡쳐해 제 로컬에 저장하겠다는 의미입니다. HTTP 요청시 사용하는 포트번호는 기본적으로 80이며, 이는 Well known 포트라고 합니다.
  1. 첫 번째 줄의 Source(출발지 주소)는 제 ip 주소, Destination(목적지 주소)는 google.com의 IP 주소입니다.
    1. 제가 클라이언트의 입장으로 서버에서 데이터를 받아오기 위해 연결을 시작한다는 의미이죠.
    2. 송신측에선 첫 번째 패킷이니 SYN 플래그를 1, 시퀀스 번호를 0으로 사용해 패킷을 전송합니다.
  2. 두 번째 줄에서 출발지 ip주소와 목적지 ip주소가 변경되어 google 서버에서 SYN, ACK 플래그로 응답해주는 모습입니다.
    1. 이 때, 수신측에서 보내주는 첫 패킷이니 시퀀스 번호는 0, 응답 번호는 수신 측의 시퀀스 번호에 1을 더해 보내줍니다.
    2. 수신측의 응답을 잘 받았으니 송신측은 송신 측의 Ack 번호를 시퀀스 번호로 설정하고, 응답 번호는 수신 측의 시퀀스 번호에 1을 더합니다.
  3. 네 번째 줄에서 서로 간 통신을 하기 위한 사전 연결 작업이 끝났으니, 송신 측에서는 그제서야 HTTP GET 요청을 보냅니다.

  4. 5 ~ 15번째 줄에선 서버 측에서 요청에 대한 응답 데이터를 보내는 과정입니다.

  5. 16 ~ 17 번째 줄은 클라이언트 측의 Window Size( 현재 받을 수 있는 패킷의 양)을 보내줍니다- [TCP Window Update].
    1. 이후 클라이언트 측의 윈도우 사이즈를 확인한 서버 측은 클라이언트 측이 윈도우 사이즈가 넉넉하니, 계속해서 데이터를 전송합니다.
    2. 이때, 서버 측은 패킷을 전송할 때, TCP 헤더에 PSH 플래그를 설정해 보냅니다.
    3. PSH 플래그는 서버 측에서 전송할 데이터가 없거나 데이터를 버퍼링 없이 응용 프로그램으로 즉시 전달할 것을 지시할 때 사용됩니다.
    4. 또한, HTTP status 200 으로 성공적으로 응답이 완료되었다는 패킷도 함께 전송합니다.
  6. 27 ~ 31 번째 줄 . 모든 패킷이 잘 분해되고 조립되어 클라이언트 - 서버 측 간 연결을 종료하게 됩니다. (4방향 핸드셰이크)  
    1. 이때는 FIN 플래그를 사용하게 됩니다.
    2. FIN 플래그는 연결 종료 시 1로 표시되며, 위와 같이 데이터 전송을 마친 후 정상적으로 양방향 종료 시 사용됩니다.

 

결론 및 참고

이로써, 클라이언트 - 서버 간 통신 상황에서 3방향 핸드셰이크가 어떤 과정으로 이루어지는 지 직접 확인해보았습니다.

 

참고로 정상적으로 양방향 종료 시, FIN 플래그가 1로 표시 되어 사용된다고 했는데 실제 서비스를 운영하는 과정에서 서버 측에서 프로세스가 종료 되었거나 의도치 않는 동작을 했을 경우, 서버 측에서 보낸 패킷에서 RST 플래그가 표시 될 수도 있습니다. 

 

RST 플래그 또한 연결 종료 시 1로 표시되어 사용되지만, FIN 플래그와 달리 연결 강제 종료를 위해 연결을 일방적으로 끊을 때 사용됩니다.(서버 측이던, 클라이언트 측이던 무엇인가 문제가 생겼다는 뜻이겠죠 ? )

 

이번 글에서는 3방향 핸드 셰이크 과정을 보기 위해 해당 과정 패킷을 중점적으로 보았지만,

 

이런식으로 패킷을 캡쳐하여 어떤 부분에서 문제가 일어났는지 분석을 할 수 있다면 , 서비스 운영 및 여러 문제 해결에 도움을 더 줄 수 있을 것 같습니다. 끝으로 지인 분께서 저한테 해주셨던 말씀으로 이 글을 마치도록 하겠습니다.

패킷은 절대 거짓말을 하지 않는다.
- 어느 훌륭한 네트워크 엔지니어(CCIE) - 

'Network' 카테고리의 다른 글

TCP 송/수신 원리  (8) 2023.09.03
DHCP ? (Dynamic Host Configuration Protocol)  (5) 2023.08.09

오늘은 TCP Segment 송수신 원리에 대해 알아보겠습니다.

 

클라이언트와 서버 간 서로 통신을 위해 TCP로 연결 된 모습을 간략하게 나타낸 그림입니다.

TCP 프로토콜로 연결하기 전, 3-way handshake 과정은 다음번에 더 자세히 다루도록 하겠습니당.

간단하게 설명하자면 3-way handshake는 클라이언트와 서버 간 데이터를 주고 받기 전에 서로간의 연결 상태(SYN, ACK)를 확인하는 과정을 말합니다.

먼저, 위 그림 같은 상황에서 서버가 클라이언트로 응답 데이터를 보낼때 서버에서 일어나는 상황을 볼까요 ?

 

서버의 프로세스에서 클라이언트 쪽으로 하드웨어 영역에 있는 파일을 전송한다 가정했을 때, 서버 쪽 프로세스는 해당 파일을 처리하기 위해 메모리에 적재를 합니다.

하드웨어 영역의 파일은 커널의 파일 시스템에 의해 관리되며, 하드 디스크는 소프트웨어 영역의 하드 디스크 드라이버에 의해 관리되겠죠.

 

이 때, 할당되는 메모리가 전송하려는 파일 크기보다 작다면, 할당된 메모리 크기에 맞춰 파일을 분할하여 메모리에 적재하게 됩니다.

(메모리만 받쳐준다면...많이 적재하는게 좋긴 하겠죠 ..ㅎㅎ) 

 

바로 아래 그림과 같이 말이죠.

전송하려는 파일 크기는 1.4MB, 할당된 메모리는 64KB이며 64KB 단위로 분할

이후, 서버 측 프로세스에선 데이터를 전송하기 위해 TCP/IP 프로토콜 스택에서 TCP Buffer에 현재 메모리에 적재되어 있는 분할된 데이터를 담게 됩니다. 

분할된 데이터가 메모리에서 TCP Buffer로 카피된다고 볼 수 있죠.

이 과정을 Buffered I/O라고 부릅니다.

 

TCP 버퍼에 카피된 데이터가 Internet 계층으로 인캡슐레이션 과정이 일어나게 되고, 이 과정에서 데이터가 세그먼트로 분할 되며 , 각각의 세그먼트들에게는 번호가 부여되게 됩니다. ( 예시 : 1번 세그먼트 + 2번 세그먼트 + 3번 세그먼트 = 보내려는 데이터 )

응답 서버 측 입장에선 4-3-2-1 순으로 패킷이 인캡슐레이션 된다.

분할된 세그먼트를 순서대로 패킷에 담아 스위치와 라우터 등을 거쳐 클라이언트 쪽으로 전송하게 됩니다. 

 

이 때, 당연히 해당 패킷에는 목적지 주소 정보도 같이 포함되어 있습니다.

 

성공적으로 패킷이 클라이언트 측으로 도착하면, 인캡슐레이션 된 패킷을 디캡슐레이션을 하게 됩니다. (택배 상자가 오면 하나하나 까듯이요 )

  1. 클라이언트 측 Internet 레이어 수준에서 패킷을 분해합니다. 
  2. 클라이언트 측 Transport 레이어 수준에서 세그먼트를 확인합니다.

클라이언트 측 OS 모델

서버 측에서 분할된 세그먼트는 다시 클라이언트 측 TCP 버퍼에 그대로 적재되게 됩니다. 

 

예시 상황으로 위 과정을 다시 쉽게 설명드리자면

  1. 서버 측에서 분할된 데이터 ( 1, 2번 세그먼트) 를 패킷에 적재합니다.
  2. 해당 패킷에 목적지 주소 정보와 포트 번호를 함께 붙입니다.
  3. 서버 -> 클라이언트로 패킷을 전송 후, 클라이언트에선 다시 패킷을 분해해 클라이언트의 TCP/IP 프로토콜 스택 상위 계층으로 올려보냅니다.
  4. 클라이언트 측은 1,2번 세그먼트를 성공적으로 받았다는 응답 (ACK)을 서버측으로 보내게 됩니다.

그 이후 서버 측에서 만약 더 보내야할 데이터가 있을 경우에는 위의 4번 과정의 ACK를 Wait하게 됩니다.

 

(왜냐하면 1, 2번 세그먼트를 클라이언트에서 성공적으로 받았다는 응답이 있어야 3번 세그먼트를 전송할 수 있기 때문이지요)

 

여기서 중요한건, 위의 4번 과정에서 서버 측으로 ACK를 보낼 때, 현재 TCP Buffer에서 남아있는 공간 ( Window Size)도 같이 전송하게 됩니다. (수신의 경우 버퍼는 OS에서 관리하는 수신 버퍼를 사용합니다)

클라이언트 측의 세그먼트를 처리할 수 있는 공간이 얼마 남았다, 라는 것을 보내준다는 의미라고 생각하시면 됩니다.

 

여기서 클라이언트의 ACK( 1, 2번 세그먼트 잘 받았어 3번 보내줘) 응답을 받은 서버는 3번 세그먼트를 보내기 전, 클라이언트의 윈도우 사이즈를 확인하고, 보낼지 말지 결정하게 됩니다.

 

만약, 보내려는 세그먼트 사이즈가 클라이언트 측의 윈도우 사이즈 보다 작으면 당연히 보내게 되는데, 이 반대의 상황이라면

Wait가 걸려버리게 됩니다.

여기서 서버측의 Wait가 발생하는 상황은 여러가지가 있을 수 있겠지만,

이 경우에서는 클라이언트 측의 파일 처리 속도가 네트워크 데이터를 수신하는 속도보다 느릴 경우, 클라이언트 측의 충분한 버퍼 사이즈가 확보되지 않아 발생할 수 있습니다.

그래서 보통 클라이언트 측에서는 데이터를 읽자마자 처리 루틴을 별도 스레드로 분리해 대응한다고 합니다.

만약 데이터를 읽고 처리하는 코드를 한 스레드 안에 묶어버리면 위와 같이 장애 가능성이 매우 높아집니다.

 

여기까지 서버와 클라이언트 측의 데이터가 어떤 식으로 전송되는지 알아보았습니다 !

 

ref : https://youtu.be/K9L9YZhEjC0?si=vMtodPcxR6AAQLdS

'Network' 카테고리의 다른 글

3-way handshake  (1) 2023.11.07
DHCP ? (Dynamic Host Configuration Protocol)  (5) 2023.08.09

오늘은 네트워크 주요 기술 중 하나인 DHCP에 대해 알아보겠습니다.

 

그전에 앞서, 호스트가 네트워크와 통신 시에 필요한 것들을 살펴볼까요?

 

첫 번째로, 물리적 네트워크가 구성이 되어 있어야 합니다. (외부망이든 내부망이든)

 

두 번째로, IP 주소, 서브넷 마스크, 게이트웨이, DNS 주소 설정 등이 더 필요하죠

 

여기서 IP 주소, 서브넷 마스크, 게이트웨이, DNS 주소 설정 등을 자동으로 해주는 것을 가리켜 정적 할당이라 하고,

 

그 외에 자동으로 설정하는 것을 동적 할당이라고 합니다.

 

최근에는 동적 할당 방식을 많이 이용하고 있는데, 이는 보안이 강화될뿐더러 저희가 수동으로 관리를 하지 않으니 관리가 쉬워진다는 장점 때문입니다

 

또한 IP를 별도로 설정하지 않아도 네트워크에 쉽게 접속이 가능합니다.

 

이걸 가능케 하는 프로토콜이 있는데요. 바로 오늘 소개할 프로토콜인 DHCP 프로토콜입니다.

 

DHCP 프로토콜은 앞서 설명했다시피, IP를 동적으로 할당하는 데 사용되는 프로토콜입니다.

 

해당 프로토콜을 사용하면 네트워크와 통신 시에 필요한 각종 설정들을 자동으로 할당받아 사용할 수 있습니다.

 

요약하자면,

  1. 별도의 IP 설정 작업이 필요 없어 사용자와 관리자 모두 편하게 네트워크에 접속이 가능

  2. 사용하지 않는 IP 정보는 회수되며 사용하는 경우에만 재할당이 됩니다

  3. 자동으로 할당되니 사용자가 직접 입력하며 발생할 수 있는 사소한 실수까지 예방이 가능합니다.

 

DHCP 동작방식

Discover 부터 DHCP 서버로의 Ack 과정

그럼 우리 클라이언트가 DHCP 서버에서 어떤 식으로 동적할당을 받는지 한 번 알아보겠습니다.

 

1. DHCP Discover ( 서버야 어딨니, 나 뭐 쓸지 좀 알려줘)

DHCP 클라이언트는 DHCP 서버를 찾기 위해 DHCP Discover 메시지를 연결된 네트워크상에서 브로드캐스트로 전송합니다.

  • 요청을 보낼 때, 출발지 MAC 주소는 클라이언트 본인으로 설정합니다.
  • 목적지 MAC 주소와 IP는 브로드캐스트 방식으로 요청합니다.
  • 아직 IP를 할당받은 상태가 아니니 Src IP는 0.0.0.0을 사용합니다.
  • IP를 할당받는 과정이므로 , 패킷을 정상적으로 주고받을 수 없으니 TCP가 아닌 UDP를 사용합니다.
  • 결국 첫 번째 과정은 DHCP 서버를 찾기 위한 메시징의 과정이라 볼 수 있습니다.

 

2. DHCP Offer (나 여깄어, 이거 쓰면 돼)

  • DHCP 서버는 DHCP Discover를 수신합니다.
  • DHCP 서버는 클라이언트에 할당할 IP 주소와 서브넷, 게이트웨이, DNS 정보, Lease Time( 해당 정보 *임대 시간  )의 정보를 포함한 DHCP 메시지를 클라이언트로 전송을 해줍니다.
    • 여기서 클라이언트에 할당할 IP 주소는 DHCP IP Pool 중에서 선택하게 됩니다. ( DHCP 서버가 보관하는 IP 리스트라 생각하시면 됩니다. )
    • IP 리스트들은 우리가 임의로 설정도 가능합니다. ( MacOS 기준 시스템 설정 -> 네트워크 -> 고급 사항)
*임대 시간 ?

클라이언트가 DHCP 서버로부터 할당 받은 정보들을 사용(임대) 할 수 있는 시간을 뜻합니다. 만료가 된다면 다시 1번의 과정을 거쳐야 할 것 같지만, 

실제로 저렇진 않고, 쓰는 도중에 새로 갱신을 합니다. ( 마치 AccessToken과 RefreshToken 처럼요 . )

리프레쉬 과정은 임대 시간중 50%가 지날 때 쯔음 진행되며

DHCP Discover(1번 과정)과 Offer(2번 과정)을 생략 한 채, 클라이언트 요청 ( Request ) 서버의 응답( Ack)를 통해 갱신됩니다.

1, 2번 과정을 생략하기 때문에 불필요한 브로드캐스트가 발생하지 않고, 유니캐스트로 진행되기 때문에 시간 또한 짧습니다.

만약 리프레쉬 과정에서 클라이언트가 응답하지 않는다면 사용하던 IP를 다시 서버에 반납하게 됩니다.

 

3. DHCP Request ( ㅇㅋ 이거 지금부터 쓴다 그럼?)

  • DHCP 클라이언트는 DHCP 서버로부터 제안받은 IP 주소와 DHCP 서버 정보를 포함한 요청 메시지를 브로드 캐스트로 다시 전송합니다.
여기서 한 가지 의문이 생기실 수 있습니다. 2번째 과정에서 클라이언트는 서버로부터 받은 메시지 안에 정보들이 담겨있을 텐데, 왜 유니캐스트로 전달하지 않고, 브로드 캐스트로 전달할까? 

여기에는 세 가지 이유가 있습니다. 

1.  DHCP 서버 여러 대가 동작하는 환경 일 수 있기 때문에 이를 위해 기본적으로 브로드 캐스트 방식을 사용합니다.

2. DHCP 서버 여러 대가 운용 중인 환경에서 클라이언트는 서버로부터 Offer 메시지를 여러 개를 수신 받고, 하나만 골라 Request 메시지를 보냅니다.

3. 위의 과정에서 브로드캐스트가 수행되며 여러 서버에서는 해당 Request 메시지가 자기것인지 확인하고 아니라면 패킷을 드랍시킵니다.

 

 

4. DHCP Acknowledgement ( ㅇㅇ 지금 부터 쓰는 거 확인했엉, 기록해둘게 )

  • 3번 과정을 통해 DHCP 클라이언트가 제공해준 IP 주소를 사용하겠다는 요청을 받습니다.
  • DHCP 서버는 해당 요청을 수신 후, 해당 IP를 누가, 언제부터 사용했는지를 기록합니다 ( 아마 Pool 때문에 그런 것 같습니다.)
  • 그 후, DHCP 클라이언트에게 확인했다는 메시지를 보내게 됩니다.
더보기

🛎️ 짤막 상식 !

 

IP Pool 에서 할당 가능한 IP가 모두 할당이 된 경우에는 새로운 클라이언트에서 IP 요청이 들어와도 IP를 내 줄 수 없게 되는 상태가 벌어집니다.

 

이를 악의적으로 이용해 DHCP 서버에서 가용한 모든IP를 가짜로 할당받아,

 

실제 클라이언트가 IP 주소를 할당받지 못하게 하는 공격 방식을 DHCP Starvation(기아 상태) 공격이라고 합니다 

 

 

여기까지 DHCP란 무엇이며, DHCP의 동작 방식을 살펴보았습니다.

 

'Network' 카테고리의 다른 글

3-way handshake  (1) 2023.11.07
TCP 송/수신 원리  (8) 2023.09.03

 Unix 계열의 시스템에서 권한을 통해 제어하는 대상은 File & Directory 이다.

 

즉, 어떠한 사용자(User)가 파일과 디렉토리에 대해, 어떠한 일을 할 수 있게 하거나, 할 수 없게 하는 것을

 

권한을 지정한다고 한다.

 

파일 읽기 & 쓰기 & 실행 요런 것들의 실행 여부를 지정해 주는 것을 Permission이라고 한다.

 

백문이 불여일견, Unix 계열의 시스템에서 특정 파일의 정보를 보여주는 ls -l 명령으로 파일을 살펴보자.

예시 파일 ls -l perm.txt로 해당 파일의 정보를 출력

해당 정보가 출력 됐을 때, 구분할 섹터는 위 사진과 같이 나눌 수 있다. ( | 로 구분 된 것을 섹터라고 칭하겠다)

 

각 섹터 별로 무슨 의미인지 알아보자.

 

첫 번째 섹터 [ - ]

  • 현재 파일의 타입을 나타낸다. 즉, 위 사진에서 perm.txt 파일은 파일이름에서 유추할 수 있듯이 단순한 text 파일이다.

  • 파일이라면 - 로 표시되고, 만약 파일이 아닌 디렉토리라면 해당 부분은 d로 표시된다.

두 번째 섹터 [ rw- rw- r--] (파일(perm.txt)의 사용자 별 접근 권한)

현재 이런 식으로 rw-rw-r-- 표시 되어 있는데, 이는 다시 rw- | rw- | r-- 세 부분씩 잘라서 봐야 한다.

  • 사진의 중간 섹터를 보면 egoing egoing이라는 것을 볼 수 있는데, 이는 각각 파일(perm.txt)에 해당하는 주인(Owner)과 그룹을 나타낸다.

  • 첫 번째로 오는 rw- 는 파일(perm.txt)의 주인(egoing)이 파일에 대해 행할 수 있는 권한을 뜻한다.

  • 이것을 토대로 다시 해석해 본다면 현재 파일(perm.txt)주인(egoing) 유저는 파일에 대해 r : read, w : write만 할 수 있다는 뜻이다.

  • 마지막 -는 x : execute이며, 해당 파일을 실행할 수 있는지 없는지에 대한 설명이라 볼 수 있다. ( - 로 표시 되어 있으니 실행은 불가)

  • 만약 egoing이라는 유저가 실행 권한까지 있다면 앞의 세 글자는 rwx로 표시될 것이다.

  • 두 번째로 오는 rw-는 파일의 그룹(egoing)의 권한을 뜻한다.
    -> 해당 파일에 대한 그룹은 r : read, w : write 만 가능하며 , x : execute 부분은 -로 표시되었기에 실행 권한은 없음을 뜻한다.

  • 세 번째로 오는 r--는 소유자(Owner)도 아닌 그룹(Group)도 아닌 다른 Other의 파일에 대한 접근 권한을 뜻한다.
    -> 오직 r: read만 가능하며, w & x는 -로 표시된 것으로 보아, OS내 다른 유저는 쓰기 및 실행 권한은 없음을 뜻한다.

 

세 번째 섹터 1?  (네 번째 섹터 0은 파일 크기를 의미한다)

  • 파일 액세스 모드 (두 번째 섹터)와 파일 소유자(Owner) 사이에 특이하게 숫자 1이 적혀있는 것을 볼 수 있다.
  • 여기서 숫자 "1"은 해당 파일의 *하드 링크 수를 의미한다.
  • 일반적으로 리눅스에서 파일을 생성 시에, 해당 파일의 초기 하드 링크 수는 1이며, 새로운 하드 링크를 생성할 때마다 해당 수가 증가한다.

 

*하드 링크

 

하드 링크(Hard Link)란 파일 시스템에서 한 파일에 대해 다른 파일 이름을 더 생성하는 것을 말한다.

따라서 동일한 내용을 가지고 있는 하나의 파일에 대해 다른 파일이름(하드 링크)을 만들면, 하나의 파일이 여러 개의 이름을 가지게 된다.

하드 링크는 원본 파일과 데이터 블록을 공유하므로, 원본 파일과 하드 링크 간에는 실제 데이터 복제가 발생하지 않는다.

어느 하드 링크를 통해 파일을 수정 시에도데이터 블록에 저장된 내용이 변경되기 때문에 다른 모든 하드 링크에서도 동일한 내용을 확인이 가능하다.

예를 들어, 'file1'이라는 파일에 대해 하드 링크인 'link1'과 'link2'를 생성 시에, 세 개의 이름 (file1, link1, link2)이 하나의 *데이터 블록을 가리키게 된다.

이들은 동일한 파일의 여러 이름이며, 데이터 블록을 변경하면 모든 이름에서 동일한 변경이 반영된다


*데이터 블록

 

데이터 블록은 파일시스템에서 파일의 실제 내용을 저장하는 데 사용되는 블록이다.

리눅스 파일 시스템은 하드 디스크와 같은 저장 장치를 효율적으로 사용하고 파일을 관리하기 위한 구조를 제공한다.

파일 시스템은 블록 단위로 데이터를 저장하며, 각 블록은 일정한 크기를 갖는다. 

일반적으로 데이터 블록의 크기는 여러 킬로바이트(KB)로 이루어지며, 파일이 생성될 때, 파일 시스템은 해당 파일의 여러 내용을 여러 데이터 블록에 나누어 저장한다.

예를 들어, 10KB 크기의 파일을 생성한다고 가정해 보자. 파일 시스템은 이 파일을 저장하기 위해 적절한 개수의 데이터 블록을 할당한다.

만약, 4KB 크기의 데이터 블록을 사용한다면 이 10KB 파일은 3개의 데이터 블록에 저장된다. 첫 번째 두 블록은 각각 4KB, 마지막 블록은 2KB로 채워진다.

데이터 블록은 파일의 내용을 저장하는 데에만 사용되는 것이 아니라, 메타데이터( 파일 권한, 소유자, 생성 시간 등)도 저장된다.

이러한 메타데이터 블록과 파일 내용 블록은 파일 시스템의 논리적 구조를 구성하게 된다.

여기에 조금 더 설명을 추가하자면, 원본 파일과 하드 링크는 동일한 데이터 블록을 가리키기 때문에, 파일의 내용을 변경하면

데이터 블록이 수정되고 , 원본 파일과 하드 링크 모두 변경된 내용을 반영하게 된다.

이로 인해 파일들은 서로 동일한 내용을 공유하게 되며, 저장 장치의 공간을 효율적으로 활용할 수 있게 된다.

 

 

 

문제점

AWS ECS 클러스터 인프라 인스턴스 유형을 amd64에서 arm64로 변경 후, GitActions에서 배포가 되지 않는 상황이 발생했다.

 

GitActions에서 제공하는 기본 러너 환경이 X86_64 우분투 서버라, 해당 서버에서 빌드한 이미지는 arm64 인스턴스에서 실행을 할 수가 없었다.

 

따라서 배포할 인스턴스 아키텍쳐에 맞게 GitActions에 여러가지 시도를 해보았다.

 

1.  워크플로우 yml 파일의 도커 이미지 빌드 과정에서 docker buildx로 멀티플랫폼 이미지 빌드

워크플로우 yml에 Docker buildx 환경 설정
하단 명령어에 --platform 플래그를 설정해주고 이미지 아키텍처를 설정

위의 방법으로 빌드 시점에 이미지 플랫폼을 설정해주었지만...

 

이상하게도 워크 플로우 내에서 이미지 빌드 자체가 되지 않는 문제가 발생했다..

 

다음 방법으로 넘어가보자.

 

2. GitActions 러너 환경 자체를 arm64로 변경하기

Git에서는 GitActions의 런타임 환경레포지토리 별로 다른 서버를 호스팅해 사용 가능하게 지원한다. (기본적으로는 Ubuntu)

 

실행 가능한 환경은 MacOS, Linux, Windows 셋 다 가능하며 설정법은 꽤나 간단하다.

 

먼저, 레포지토리의 Settings에 접근 후, 좌측 메뉴의 Actions - runners 탭에 들어간다.

리눅스 뿐 아니라 다른 OS도 지원하는 모습. macOS도 x64, arm64 지원.
필자의 경우는 배포할 서버의 환경에 맞게 설정해주었다.

위와 같이 설정을 하게 되면 (접근 가능한) 서버에서 실행할 명령어를 보여주게 된다.

 

런타임을 제공할 해당 서버에 접속 후, 하단에 나오는 명령어를 차례대로 입력 하면 된다.

( 본인은 배포 환경에 맞는 도커 이미지를 맞춰주기 위해 arm64 EC2 인스턴스를 사용했다. )

 

 

Download Step

# Create a folder
$ mkdir actions-runner && cd actions-runner

# Download the latest runner package
$ curl -o actions-runner-linux-arm-2.307.1.tar.gz -L https://github.com/actions/runner/releases/download/v2.307.1/actions-runner-linux-arm-2.307.1.tar.gz

# Extract the installer
$ tar xzf ./actions-runner-linux-arm-2.307.1.tar.gz

각 명령어들이 성공 되었으면 actions-runner 디렉토리에 다음과 같은 파일들이 생성 되어 있는 모습을 볼 수 있다.

 

Configure Step

# Create the runner and start the configuration experience
$ ./config.sh --url https://github.com/USER_NAME/REPOSITORY_NAME --token TOKEN

# Last step, run it!
$ ./run.sh

참고로 Git에서 EC2 서버로 접속하는 방식이 아닌 EC2 서버에서 GitHub 저장소로 접속하는 방식이다.

 

그래서 /USER_NAME/REPOSITORY_NAME 필드는 해당 github 저장소 주소,

--token TOKEN 액세스 토큰으로 설정해줘야 한다.

 

또한 위의 config.sh 스크립트를 실행할 때

 

해당 에러를 마주칠 수 있다. 이 에러는 GitHub Actions Runner가 실행 중에

 

ICU (International Components for Unicode) 패키지를 찾지 못해 발생하는 문제이다.

 

ICU는 국제화 및 지역화를 지원하는 라이브러리로, GitHub Actions Runner가 이를 사용하지 못하면 오류가 발생 할 수도 있다.

 

호옹...그렇다고 한다...

어찌됐든 해당 에러는 ICU 패키지를 찾지 못해서 발생하는 문제이므로 ,

 

해당 라이브러리는 현재 configuration 과정에서 필요 없으므로 이를 무시하는 옵션을 넣고 실행하면 된다.

 

DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true ./config.sh --url [저장소 주소] --token [토큰]

위의 스크립트가 성공적으로 실행되면 , 아래와 같이 Connected to GitHub 메시지를 볼 수 있다.

 

 

 

마지막으로 runner를 실행 시키는 스크립트 까지 실행해보자

DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true ./run.sh

성공적으로 서버에서 GitHub로 연결된 모습이며,

이때 action - runner 프로세스는 Job(Actions에서 발생한 워크플로우)을 Listening 상태로 대기하게 된다.

다시 Actions - Runner 탭에 들어가서 확인해보면 해당 레포지토리에 self-hosted runner 잘 등록되어 있는 걸 볼 수 있다.

 

runner Status가 정상인 걸 확인했으면 워크플로우 yml 파일을 다음과 같이 수정해주자.

jobs:
  deploy:
    name: Deploy
    runs-on: self-hosted
    environment: production
  • runs-on : 요 부분을 위에서 등록한 서버의 태그를 달아주면 된다. (만약 등록한 러너가 하나라면 self-hosted만 달아줘도 된다)
  • 만약 여러 개의 러너를 등록한 경우에는 등록한 러너의 태그에 따라
  • runs-on : [ self-hosted, Other(러너 목록의 해당 태그) , Other(러너 목록의 해당 태그) ]
    이런 식으로 세부적으로 분리도 가능하다.

 

이 후 , GitActions에서 워크 플로우를 실행 시킨 후, 연결된 서버에 접속하면,

해당 job이 워크플로우 순서대로 성공적으로 실행되고, 성공하는 모습을 볼 수 있다.

(이거이거...깃액션에서 도커 레이어 캐시도 더 잘 될지도..?)

ref : 

https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/adding-self-hosted-runners

+ Recent posts