✅ zappa로 lambda에서 django 서버 배포하기

현재 상황을 간단히 정리해보고 해결방법을 찾아보자. 우선 본문에 쓰인 제목처럼 zappa를 통해 lambda 함수를 만들고 실행시킨 다음, API 게이트웨이와 연결짓는 것이 목표였다. 

 

처음에 zappa를 설치하고 시작한 다음 아래 명령어를 통해 배포를 시도해봤다. 

pip install zappa
zappa init
zappa deploy dev

 

그런데 다음과 같은 오류가 났다.

Traceback (most recent call last):
  File "/Users/soyoung/Projects/backend/onestep_dev/lib/python3.12/site-packages/zappa/cli.py", line 804, in deploy
    self.lambda_arn = self.zappa.get_lambda_function(function_name=self.lambda_name)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/soyoung/Projects/backend/onestep_dev/lib/python3.12/site-packages/zappa/core.py", line 1419, in get_lambda_function
    response = self.lambda_client.get_function(FunctionName=function_name)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/soyoung/Projects/backend/onestep_dev/lib/python3.12/site-packages/botocore/client.py", line 565, in _api_call
    return self._make_api_call(operation_name, kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/soyoung/Projects/backend/onestep_dev/lib/python3.12/site-packages/botocore/client.py", line 1021, in _make_api_call
    raise error_class(parsed_response, operation_name)
botocore.errorfactory.ResourceNotFoundException: An error occurred (ResourceNotFoundException) when calling the GetFunction operation: Function not found: arn:aws:lambda:ap-northeast-2:264760273909:function:backend-dev

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/soyoung/Projects/backend/onestep_dev/lib/python3.12/site-packages/zappa/cli.py", line 3048, in handle
    sys.exit(cli.handle())
             ^^^^^^^^^^^^
  File "/Users/soyoung/Projects/backend/onestep_dev/lib/python3.12/site-packages/zappa/cli.py", line 521, in handle
    self.dispatch_command(self.command, stage)
  File "/Users/soyoung/Projects/backend/onestep_dev/lib/python3.12/site-packages/zappa/cli.py", line 563, in dispatch_command
    self.deploy(self.vargs["zip"], self.vargs["docker_image_uri"])
  File "/Users/soyoung/Projects/backend/onestep_dev/lib/python3.12/site-packages/zappa/cli.py", line 838, in deploy
    self.lambda_arn = self.zappa.create_lambda_function(**kwargs)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/soyoung/Projects/backend/onestep_dev/lib/python3.12/site-packages/zappa/core.py", line 1145, in create_lambda_function
    response = self.lambda_client.create_function(**kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/soyoung/Projects/backend/onestep_dev/lib/python3.12/site-packages/botocore/client.py", line 565, in _api_call
    return self._make_api_call(operation_name, kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/soyoung/Projects/backend/onestep_dev/lib/python3.12/site-packages/botocore/client.py", line 1021, in _make_api_call
    raise error_class(parsed_response, operation_name)
botocore.errorfactory.InvalidParameterValueException: An error occurred (InvalidParameterValueException) when calling the CreateFunction operation: Unzipped size must be smaller than 262144000 bytes

==============

Need help? Found a bug? Let us know! :D
File bug reports on GitHub here: https://github.com/Zappa/Zappa
And join our Slack channel here: https://zappateam.slack.com
Love!,
 ~ Team Zappa!

 

GPT에게 분석을 의뢰하니 크게 두 가지의 원인이 있다고 했다. 

  1. AWS 프로파일(profile) 정보가 잘못 입력되었을 가능성
  2. python 패키지가 최대 크기인 250MB를 초과했을 가능성

그래서 두 가지의 가능성을 체크한 다음 하나씩 고쳐 주었다. 

 

1번의 경우 아래 명령어를 입력하면 현재 AWS에서 사용하고 있는 profile 정보를 볼 수 있었다. 그런데 이 정보는 .zshrc 폴더에 있는 AWS_ACCESS_KEY 값을 갖는 profile의 정보인 것으로 추측되었다. 나는 이전에 이 값을 등록해 놨었는데, 그때는 현재는 만료된 기존의 소마 계정의 값을 가져왔었다. 이 부분을 수정해서 현재 내 IAM 계정의 access key, secret key 값을 넣어주었다. 

aws configure list

 

그랬더니 다음과 같이 프로필이 잘 나오더라. 

 

2번의 경우는 zappa의 설정 변수를 모아둘 수 있는 zappa_settings.json 파일(zappa init 명령어를 입력하면 해당 json 파일이 생긴다)에 다음과 같은 옵션을 추가해 주었다. 

"slim_handler": true

 

그리고 다시 아래 명령어를 입력하였더니, 일단은 중간에 에러가 나오면서 종료되지는 않았다. 다만 걸리는 부분은 이런 로그가 떴다는 것이었다. 

Deploying API Gateway..
Waiting for lambda function [backend-dev] to be updated...
Error: Warning! Status check on the deployed lambda failed. A GET request to '/' yielded a 500 response code.

 

추측상 AWS lambda 함수는 잘 만들어졌는데, 이걸 API 게이트웨이로 배포하는 과정에서는 오류가 발생한 것이라고 이해했다. 아래의 명령어로 확인해보니 AWS 람다 함수는 잘 만들어졌다. (원래는 빈 리스트가 나왔다)

aws lambda list-functions --region ap-northeast-2

 

그리고 AWS 람다 페이지에서도 확인해보니 기존엔 빈 리스트였던 페이지에 새 함수가 추가되어 있었다.

 

다만 GPT의 설명에서는 원래 'zappa deploy' 명령어를 실행하면 API 게이트웨이의 엔드포인트가 같이 떠야 한다고 했는데, 그 부분이 위에서 언급했던 에러 로그 때문에 생략된 것 같았다. 그래서 이 부분부터 다시 해 줘야 되겠다. 

 

lambda 함수를 API 게이트웨이에 배포해서 엔드포인트 얻기

lambda 레코드는 생성되었는데 API 게이트웨이 레코드는 생성이 된 것인지 확실하지 않았다. 다행히 API 게이트웨이 페이지를 확인해보니 레코드가 생성되어 있었다. 

 

그렇다면 왜 'zappa deploy' 명령어를 입력했을 때 API 게이트웨이 엔드포인트가 자동으로 나오지 않았을지는 의문이다. 나는 API 게이트웨이의 엔드포인트 URL을 얻고 싶었다. 이걸 확인하려면 어떻게 해야할지 물어봐봤다. GPT 피셜, API를 클릭한 다음에 '스테이지'를 누르면 된다고 한다. 

 

그래서 URL을 얻어냈다. 그런데 막상 엔드포인트로 접속하니 다음과 같은 에러가 떴고, 구체적인 사항은 'zappa tail' 커맨드를 통해 확인할 수 있다고 한다. 일단 확인해보자. 

zappa tail dev

 

로그를 자세히 보니 AWS secrets manager에서 관련 환경변수를 가져오는 코드에서 오류가 났다. 아차 싶었던 것은 이 코드는 이전의 소마 계정의 secrets manager를 참고하고 있었다. 해당 계정은 이미 사라진 계정이었으니 오류가 나는 것이 당연했다. 그렇다면 별도로 AWS secrests manager를 하나 만들어서, 그 안에 기존에 사용했던 환경변수 코드들을 넣어 줘야 하겠다. 

 

zappa를 통해 django 코드를 lambda에 배포하기 vs python으로 django 코드를 직접 lambda에 배포하기

말 그대로다. 약 4시간 동안 삽질을 하면서(물론 새벽 삽질이라 효율성이 좋진 않았다) django 코드를 zappa를 통해 aws lambda로 배포하는데 여러 난관에 부딪혔다. 계속해서 아래와 같은 에러가 나는 상황이었다. 

[1734632870695] LAMBDA_WARNING: Unhandled exception. The most likely cause is an issue in the function code. However, in rare cases, a Lambda runtime update can cause unexpected function behavior. For functions using managed runtimes, runtime updates can be triggered by a function change, or can be applied automatically. To determine if the runtime has been updated, check the runtime version in the INIT_START log entry. If this error correlates with a change in the runtime version, you may be able to mitigate this error by temporarily rolling back to the previous runtime version. For more information, see https://docs.aws.amazon.com/lambda/latest/dg/runtimes-update.html
[1734632870695] [ERROR] ClientError: An error occurred (UnrecognizedClientException) when calling the GetSecretValue operation: The security token included in the request is invalid.

 

이 에러를 해결하기 위해서 여러 방법을 시도했었다.

  1. IAM 콘솔에 들어가서 zappa가 생성한 역할에 SecretsManager에서 GetSecretValue 작업을 할 수 있는 권한을 추가했다. 
  2. boto3에서 사용하는 aws 계정의 세부 정보와 실제 aws의 계정 정보가 다른 것이 원인일 수 있다고 판단해서, boto3 라이브러리의 sts 관련 코드를 추가하여 임시 역할과 관련된 액세스 토큰 정보를 가져올 수 있도록 했다. 
  3. boto3 라이브러리에서 코드가 다시 update/deploy 되어도 캐시로 기존의 세션 정보를 가지고 있을 수 있다는 가능성이 있어서 boto3의 세션을 초기화하는 코드를 넣어주었다. 

그 외에도 자잘한 여러 방법을 시도했던 것으로 기억하는데, 유감스럽게도 잘 되지 않았다. 그래서 다시 몇 스텝 뒤로 돌아가서 과연 zappa를 통해 django 코드를 배포하는 것이 맞았을까? 라는 의문을 다시 가져 보기로 했다. 결국은 비용은 똑같으니 편리함의 문제였다. 마침 관련된 블로그 글을 봐서 후자의 옵션으로 마음이 조금 더 기울었다. 

 

우선은 오늘은 여기까지 코딩을 하고, django 코드를 최대한 간단하게, 외부 라이브러리를 거치지 않고 aws lambda에 배포 가능하도록 바꾸는 방법을 고안해봐야겠다.

 

 궁금한 점

  1. 'zappa deploy' 명령어의 원리는 무엇일까?
  2. slim_handler: true 라는 옵션을 붙이면 어떻게 배포 파일의 크기가 줄어드는 걸까?

 

어제의 작업으로 VPC와 그 안의 서브넷들을 설정해 주었다. 현재 이해한 그림은 다음과 같다. 

그렇다면 이제 이 서브넷들 안에다 AWS 리소스를 배치해 주어야 하겠다. 저번 포스팅에서 GPT의 조언을 참고한 것처럼, AWS lambda와 서버리스 구조를 따르려고 한다. 그런데 헷갈리는 점이 있다. 

 

기존에 ALB와 웹 서버, DB를 배치할 때에는 ALB는 퍼블릭 서브넷에, 나머지 리소스는 프라이빗 서브넷에 배치해주면 되었다. 그런데 AWS lambda라는 친구는 처음이라 여러 의문이 들었다. 

  1. 서버리스인데 어떻게 요청을 받는가(서버리스를 한 번도 안 써본 사람으로써 갖게 되는 의문)
  2. 각 AZ마다 1개의 lambda 리소스가 있는 것인가?
  3. 그렇다면 퍼블릭과 프라이빗 중 어느 서브넷에 배치되어야 하는가

 

나는 위와 같은 그림만 그리고 있기에 잘 상상이 되지 않았다. 

기존에는 ECS와 EC2 fargate 옵션을 사용하고 있었는데, 이 옵션 대신 AWS lambda와 서버리스 구조로 바꾸려고 해. 그런데 이 과정에서 드는 [의문점]이 있어. 이 [의문점]들에 대해 답변해줘

[의문점]
1. AWS lambda는 서버리스라고 이해했다. 그러면 서버리스인데 어떻게 요청을 받는 게 가능할까?
2. 각 AZ마다 1개의 lambda 리소스가 있는 걸까?
3. 그렇다면 퍼블릭과 프라이빗 중 어느 서브넷에 배치되어야 할까?

 

우선 1번 의문에 대한 답변으로는, AWS lambda가 동작하기 위해서는 '트리거(trigger)'가 필요하다고 한다. lambda에 실행 함수를 등록해두고, 트리거가 발생하면 이 함수가 실행되는 것이라고 이해했다. 그리고 서버리스라고 해서 아예 서버 자체가 없는 게 아니라, AWS에서 서버의 관리를 도맡아서 해 주는 것이었다. 

 

서버리스면 AWS 등 타 서비스가 관리를 전담해 주는 것이라서 무조건 비싸다고 생각했는데 그것은 또 아닌 것 같았다. 왜냐하면 트리거의 호출 횟수에 따라 lambda 함수가 실행되는 것이기 때문에 사용량에 따라 요금이 달라지는 것이라고 이해했다. 

 

그리고 앞서 언급한 것처럼 이 람다(lambda) 함수는 트리거에 의해 동작한다. 그 말은 보통의 서버는 항상 요청에 대해 응답을 하지만, 이 트리거는 꼭 우리가 아는 일반적인 요청의 형태가 아닐 수도 있다는 것이다. 실제로 GPT가 든 다양한 예시 중에서는 HTTP 요청뿐만 아니라 SQS 등 AWS의 타 서비스의 이벤트 발생이나 스케줄 기반의 호출도 포함되었다. 즉 사용자가 원하는 트리거의 조건을 설정할 수 있었다. 

 

2번 의문에 대한 답변으로는, 람다 리소스는 각 AZ마다가 아니라 전체 리전에 걸쳐 있는 글로벌 서비스라고 한다. 그래서 특정 AZ에 묶여있지 않는다. 사실 이 부분이 잘 이해되지 않았다. 어떤 리소스(서비스)는 AZ에 종속되어 존재하고, 또 람다와 같은 다른 리소스는 AZ와 독립적으로 전 리전에 걸쳐서 존재한다면, 그런 서비스의 데이터는 어떻게 관리되는지는 아직 잘 이해하지 못했다. 일단 넘어가 보자. 

 

3번 의문에 대한 답변으로는, AWS 람다는 VPC 외부에서 VPC와 독립적으로 실행되는 것이 기본값이라고 한다. 그러나 설정에 따라서 이 람다를 프라이빗 서브넷에 위치시키는 것도 가능하다고 한다. 그러면 어떻게 하는 것이 맞을까? 몇 번의 추가 질의를 통해 모호하지만 나름의 답을 찾아보았다. 

 

그러면 3번 질문의 경우, 만약 lambda 함수가 프라이빗 서브넷에 있는 RDS에 접근해야 한다면 이 경우 lambda를 프라이빗 서브넷에 위치시키는 것이 나을까?

 

GPT 피셜 그렇다고 한다. 람다를 VPC 외부가 아닌 VPC 내부의 서브넷에 위치시켜야 할 때, 보통 퍼블릭 서브넷에는 배치되지 않는다고 한다. 왜냐하면 퍼블릭 서브넷은 프라이빗 서브넷과의 차이점이 IGW(인터넷 게이트웨이)를 통해 인터넷 접근이 가능하다는 차이점이 있는 건데, 람다는 기본적으로 실행 환경에서 인터넷 연결을 처리하기 때문에 그럴 필요가 없다. 그렇다면 프라이빗 서브넷에 위치시켜야 VPC 내부 통신을 통해 프라이빗 서브넷 안에 있는 RDS와 통신할 수 있겠다. 

 

그러면 lambda를 프라이빗 서브넷에 연결하면 외부에서 lambda 함수는 어떻게 호출할 수 있어?

 

AWS 람다는 프라이빗 서브넷에 위치해도 무관하다고 한다. 왜냐하면 API Gateway라는 또 다른 서비스를 통해 호출될 수 있기 때문이다. API 게이트웨이(Gateway)는 특정 VPC나 서브넷에 속하지 않은, AWS 글로벌 네트워크에 속하는 서비스라고 한다. 그래서 퍼블릭 엔드포인트를 가지고 외부에서 접근할 수 있다. 

 

그러면 API 게이트웨이에서는 어떻게 프라이빗 서브넷에 있는 AWS 람다를 호출한다는 걸까? 현재까지는 VPC 밖에 있는 리소스에서 VPC 안의 프라이빗 서브넷에 위치한 리소스를 호출하는 것이 불가능하다고 알고 있기에 의아했다. 역시 질의를 해 보았는데, 알고보니 새로운 'AWS 내부 네트워크'를 통해 API 게이트웨이가 람다를 호출하는 것이 가능하다고 한다. 

 

그려보면 이런 그림이 되겠다. 

 

그리고 이렇게 AWS 람다를 프라이빗 서브넷에 위치시켰을 때, 현재 우리 서비스에서는 외부로 chatGPT API를 호출하는 API가 있다. 이 경우는 NAT 게이트웨이를 이용해서 처리할 수 있다고 한다. 그러므로 이 경우도 문제 없겠다. 그렇다면 이제 AWS 람다를 만들고 프라이빗 서브넷에 위치시켜 보자. 


'lambda'를 검색하니 함수를 실행할 수 있는 페이지가 보였다. 그런데 나는 아예 Django 웹 서버 코드의 실행을 람다로 대신하고 싶은 거였어서, 단순한 함수 하나를 실행하려는 것이 아니었다. 이 경우에는 어떻게 하면 좋을지 모호했다. 

[의문점]에 대해 해결책을 제시해줘

[의문점]
나는 아예 Django 웹 서버 코드의 실행을 AWS lambda로 대신하고 싶은 거였어서, 단순한 함수 하나를 실행하려는 것이 아니었다. 이 경우에는 어떻게 하면 좋을까?

 

이 경우에는 추가 작업이 필요하다고 한다. Django처럼 실행 시간이 비교적 긴 프레임워크를 람다에서 실행하려면 'AWS 람다용 호환 Wrapper'를 사용해야 하고, Django의 경우 대표적으로 Zappa를 사용한다고 한다. Zappa를 검색해 보니 'serverless python' 이라는 문구가 눈에 띄었다. 일단은 서버 없이 파이썬을 실행하게 해 주는 도구라고 이해해봤다. 그러면 뭘 어떻게 하면 된다는 걸까? 이 부분은 내일 알아보자. 

 

궁금한 점

  1. AWS lambda의 설정을 기본값으로는 VPC 외부에 위치시키고, 필요에 따라 프라이빗 서브넷 안으로 들어갈 수 있도록 해 놓은 이유가 궁금하다. 왜 VPC 외부에 위치시키는 것을 기본 설정으로 했을까?
  2. AWS 내부 네트워크 통신은 왜 존재할까? 왜 VPC 밖의 글로벌 네트워크에 있는 리소스와 VPC 안의 프라이빗 서브넷에 있는 리소스가 통신할 수 있도록 해 두었을까?

 

이번주의 나의 활동도 별다른 게 없다...! 블로그라면 꾸준히 써 왔었는데 그마저도 3일 동안은 끊겼다. 최종합격을 하고 내가 치열하게 살았는지를 문득 생각해 보았다. 그 질문에 대한 답은 잘 모르겠지만, 적어도 일은 꾸준히 손에 안 놓고 해왔던 것 같았다. 그런데 그게 좀 지쳤던 것도 같았다. 

 

'지금 못 쉬면 앞으로 몇 달간은 적응하느라 바쁜데 언제 쉴까?' 라는 생각이 스멀스멀 들어서, 3-4일동안 개발을 내려놓았었다. 아예 블로그를 안 썼다. 게임을 하고, 집에서 간단한 스쿼트 운동만 했다. 잠을 12시간씩은 잔 것 같다. 새로운 웹툰을 정주행하고, '스타듀밸리'라는 농장 키우기 게임도 4시간씩은 한 것 같다.

 

덕분에 밀려있는 일들을 외면하느라 좀 힘들었다.

 

그리고 이렇게 쉬어도 내 마음이 완전히 편하지는 않았다. 뭐랄까, 자유도 어느 정도의 제약이 있어야 자유인 것이지 한없이 풀어진 상태에서는 그저 폐인처럼 사는 것 같았다. 그래서 어제부터 다시 블로그를 조금씩 잡아 보았다. 이번 주에도 느긋이, 야금야금 IaC 설정 이슈를 잡고 있었다. 그러다 문득 진척이 너무 안 나는 것 같아서 오늘에서야 IaC를 조금 내려놓고 커스터마이징으로 설정을 해 보기로 했다. (설정은 손으로 했지만 이렇게 설정한 것을 terraform 데이터로 갖고 있어 볼 생각이다..!)

 

팀원들과도 프로젝트를 계속 살려보자는 얘기를 했는데, 정작 시간이 제일 많이 남았을 내가 이러고 있어서 조금 마음의 부채감도 든다. 그러나 겨우 조그맣게 남은 시간인데 어떡하랴. 일단은 이대로 가 보는 수밖에 없다. 어차피 서비스의 운영도 꾸준함이 중요하니 말이다. 

 

결론은 밍기적거리는 일상에 대해서 조금의 죄책감과 부채감이 들었지만, 갑자기 달리다가 멈춰선 순간에 오는 쉬어가는 타이밍이라는 것도 알기에 어쩔 수 없는 일상 같다. 다만 이 일상을 '폐인'이 아니라 '여유로움'이라고 정의할 수 있도록, 나에게 필요한 최소한의 것들은 놓지 말아야 하겠다. 어떤 것들이 있을지만 적어보자. 

 

  1. 운동. 요즘은 날이 추워서 러닝은 못 하고 하루 20개씩 3세트 스쿼트 운동으로 대신하고 있다. 
  2. 매일 블로그 쓰기. 어제부터 잡고 시작해봤다. 꼭 매일이어야 하는 건 아니지만, 일주일에 3-4개 정도는 써 보도록 하자. 
  3. 바깥생활. 나는 겨울이 되면 특히 더 몸이 가라앉는다. 그렇기에 광합성을 하듯이 햇빛을 매일 쐬 주자. 

이렇게 다음 한 주까지 또 잘 지내보자:)

 

며칠간의 삽질을 하면서 느낀 점이 있다. 꼭 IaC로 모든 설정을 관리해야 할까? 라는 의문이었다. 당연히 IaC는 반복적이고 복잡한 인프라 설정을 코드로 대신할 수 있는 편리한 도구지만, 그 도구에 익숙하지 않아서 시간이 오래 걸린다면 처음에는 IaC로 할 수 있는 일부분만 구현해도 좋겠다는 생각이 들었다. 

 

그래서 어제 언급한 블로그를 찾아서 VPC 선언까지는 IaC로 해 보고, 그 외에 다른 것들은 직접 선언해보기로 했다. 그게 더 빠르게 구현할 수 있는 방법이라고 생각했다. 

 

사실 이렇게 마음먹은 이유가 또 있는데, 바로 ECS를 사용하게 되었을 때의 비용 문제였다. 소마에서 지원해주는 서버 비용을 봤을 때 AWS 인프라에서 돈이 가장 많이 들었던 두 개가 바로 ECS와 RDS였다. RDS의 경우는 구글 드라이브 동기화나, 프리 티어를 사용하는 등의 대체재가 있었다. 그러나 ECS의 경우는 이를 사용할 수 없다면 docker swarm 등의 다른 대체재를 생각해봐야 했다. 무궁무진한 방법들이 있다는 것을 알기에 GPT 질의를 통해 선택지를 좁혀 보기로 했다. 

aws에서 제공하는 여러 서비스들 중 ECS를 잘 쓰고 있었어. 그런데 비용 문제로 이제는 ECS 말고 다른 서비스를 찾아봐야 할 것 같아. CI/CD 파이프라인 구축에서 ECS와 같은 역할을 하는 유사한 다른 서비스들을 추천해줘
그러면 [내가 이해한 것]을 바탕으로 '가장 저렴한 방법'과 '초보자가 가장 쉽게 따라할 수 있는 방법'의 순위를 각각 알려줘

[내가 이해한 것]
총 5개의 옵션이 있다. 
1. aws fargate + app runner
2. google cloud run + github actions
3. aws lambda + serverless
4. docker swarm
5. kubernetes

 

GPT는 총 5개의 옵션을 제안해 주었고, 이 중에서 중요한 것이 '빠르게 구현 가능한지'와 '비용이 저렴한지'였다. 의외로 3번 옵션인 AWS lambda와 serverless 아키텍처를 결합한 옵션 모두가 저렴하면서도 초보자도 빠르게 구현할 수 있다고 했다.

 

저렴한 이유는 트래픽에 비례해서 비용이 증가하는 구조이므로 사용자가 적을 경우 비용이 많이 발생하지 않고, 서버리스이기 때문에 별도의 서버 관리 비용이 없다는 것이었다. 

 

또한 초보자가 빠르게 구현할 수 있는 이유는 함수 단위로 실행되므로 구체적인 인프라 설정이 필요 없다고 했다. yaml 파일만으로도 설정이 가능한 것으로 보였다. 

 

암튼 그렇게 해서 ECS는 AWS lambda로 대체하게 되었다. 아이러니하게도 AWS 생태계를 벗어나지 않았다..! 이러면 이제 aws lambda도 같은 aws provider 안에서 작성 및 apply가 가능하긴 한데... 일단 한 번에 너무 많은 걸 하려고 하지 말고 나에게 생소하고 복잡했던 vpc 설정만 terraform으로 대신해 보자. 

 

💻 aws vpc, subnet, availability zone에 대해 알아보기

어제의 블로그처럼만 따라하면 vpc가 생성된다. 그러면 다 된 거 아닌가?라고 생각할 수 있는데, vpc만 선언하면 되는 게 아니라 그 안에 있는 public/private subnet 등의 추가 설정이 필요하므로 아직 다 된 것이 아니다. 

 

그리고 해당 블로그를 따라하면서 의아했던 부분이 있다. 바로 private subnet이었다. 처음엔 이게 왜 필요한지 의문이었다. 보안 때문인 것이라고만 알고 있는데, 괜히 복잡할 것 같아서 선언하지 않아도 되는 게 아닌가 싶어서 질의해봤다. 

aws vpc에서 subnet을 생성할 때 public, private subnet을 생성할 수 있잖아. 이때 private subnet을 설정해야 하는 이유가 있어?

 

private subnet은 보안 목적으로 생성하는 것이 맞았다. 우선 vpc 안에 subnet 개념이 있었다. 그리고 'vpc 내부 통신'이라는 개념이 있어서 vpc 내부 리소스들은 각자가 따로 통신할 수 있었다. 이 원리도 좀 궁금했는데, 일단은 private subnet의 역할을 먼저 알고 넘어가보자. private subnet 안에 있는 리소스들에 접근하려면, 같은 vpc 안에 있는 리소스들만 이 'vpc 내부 통신'을 통해서 접근할 수 있다. 그러므로 외부에서 vpc 안의 private subnet 안에 있는 리소스에 접근하는 것이 불가능하다. 이 사실 하나만으로도 보안을 증가시킬 수 있다. 

 

그러면 private subnet 안에 있는 리소스들은 아예 외부와 단절된 게 아닌가? 라는 의문이 생길 수 있다. 정확히는 외부에서의 직접적인 호출과는 단절되어 있다. 그러나 결과적으로는 외부에서 private subnet 안에 있는 리소스들을 사용할 수 있고, private subnet 안에 있는 리소스들도 필요하다면 vpc 외부로 요청을 보낼 수 있다. 어떻게 이게 가능할까?

 

우선 인바운드 트래픽, 즉 private subnet 안의 리소스로 접근하는 경우를 보자. 이 경우는 같은 vpc 안의 다른 public subnet에 존재하는 리소스를 통해 가능하다. vpc 안에는 여러 private subnet들과 public subnet들이 존재할 수 있는데, 이 public subnet 안에 있는 리소스들이 일종의 연결다리 역할을 한다고 이해했다. 

 

예시로는 간단하게 private subnet, public subnet을 사용한 아키텍처에서 유저의 요청이 들어왔을 때를 가정해 보자. 모든 subnet들은 하나의 vpc 안에 있다. vpc 안에는 public subnet이 있고(간단함을 위해서 하나의 리전, AZ만 있다고 가정해 보겠다), 이 안에는 로드밸런서(ALB)가 있다. vpc 안에는 private subnet도 있고, 이 안에는 웹 서버와 DB가 있다. 이 상태에서 유저의 요청이 alb로 들어온다. alb는 public subnet에 있기 때문에 외부의 요청을 받을 수 있다. 

 

그러면 alb는 vpc 내부 통신을 통해서 같은 vpc 안의 다른 private subnet에 있는 웹 서버를 호출한다. 그리고 웹 서버에서도 마찬가지로 vpc 내부 통신을 통해 필요 시 같은(다른 private subnet일 수도 있는데 그건 상관없다) private subnet에 있는 DB를 호출한다. 이런 식으로 통신이 일어난다고 한다. 

 

즉 중요한 자원인 서버와 DB를 public subnet에 두어 외부 접근을 가능하게 하지 않고도 로드밸런서를 두어서 이 리소스들에 대한 보안을 증가시킬 수 있겠다!

 

그렇다면 반대의 경우인 아웃바운드 트래픽(private subnet 안에 있는 리소스에서 외부로 요청을 보내는 경우)은 어떨까? 이 경우는 특별한 NAT Gateway라는 친구가 사용된다고 한다. NAT는 network address translation, 즉 네트워크 주소 변환을 의미한다.

 

컴과 수업에서 배운 기억이 희미하게 있는데, 외부와 분리된 private subnet에서 public한 웹 서버 등으로 요청을 보낼 때 거치는 과정이다. 이 과정이 필요한 이유는 private subnet에서 사용하는 주소를 public하게 사용될 수 있는 글로벌 ip주소로 바꿔줘야 하기 때문이다. 나도 여기까지만 생각이 나서 문서를 참고했다. 

 

좀 더 구체적으로는 private subnet의 주소가 글로벌 ip주소로 변환되는 시점이 있다고 이해했다. 그 시점(경계)에 있는 라우터(경계 라우터)에서 이 NAT가 발생한다. private subnet에서 존재하는 요청자의 ip주소 대신, 글로벌 ip주소로 사용될 수 있는 주소를 송신자의 ip주소로 할당하고 패킷을 라우팅한다고 이해했다. 이것도 컴과 과목에서는 아예 하나의 챕터가 따로 있을 정도로 깊은 주제였던 기억이 있어서 일단은 여기까지만 알아보자. 

 

아무튼 private subnet을 설정하고 그 안에 웹 서버와 DB를 두어야 하는 이유는 이것만으로도 설명할 수 있겠다. 그렇다면 또 다른 의문이 생긴다. private, public subnet은 한 쌍의 페어라고 가정한다면, 한 vpc 안에 몇 개의 subnet 페어가 필요할까? 이 역시 모호한 질문이었으므로 질의를 해 보았다. 

aws vpc 안에 존재하는 private, public subnet은 한 쌍의 페어라고 가정한다면, 한 vpc 안에 몇 개의 subnet 페어가 필요할까?

 

GPT답게 하나의 정해진 답을 주지는 않았는데, 이전에 들어본 것처럼 이 페어의 개수는 '가용 영역(AZ)'의 개수에 영향을 받는다고 한다. 여기서 또 vpc와 subnet, 그리고 az(가용 영역)의 관계가 모호하게 느껴져서 추가 질의를 해 보았다. 

vpc 안에 subnet이라는 개념이 존재한다고 이해했어. 그럼 available zone(AZ)은 어떤 개념이야? AZ는 vpc, subnet과 어떤 관계가 있는지 알려줘

 

정의를 살펴보자면 다음과 같다. AZ는 하나의 리전(region) 내에 존재하는 독립적인 물리적 데이터 센터라고 한다. 그리고 vpc는 하나의 리전에 걸쳐 존재한다. 반면 vpc 안의 subnet은 각 az별로 존재한다. 그러니까 하나의 vpc를 선언한 다음, 해당 vpc가 존재하는 리전에 총 몇 개의 az가 있는지에 따라서 private/public subnet 페어의 개수가 달라지겠다. 보통은 az의 개수만큼 페어를 선언하는 것이 일반적이라고 한다. 그 이유는 고가용성과 장애 대응을 위해서라고 한다. 

 

어쨌든 결론은 나왔다. 특수한 상황이 아닌 이상 ALB 등에서도 모든 AZ에 걸쳐 리소스를 배포하도록 설정되어 있다고 한다. 그리고 나는 특수한 상황은 아니니, 일단은 모든 AZ(나의 경우에는 ap-northeast-2 리전의 2a, 2b, 2c, 2d AZ가 되겠다)에 리소스를 배포하도록 설정하자. 그러려면 총 4쌍의 private/public subnet 페어들이 필요할 것이다. 

 

그런데 이렇게 보니 vpc를 만드는 것 외에도 private, public subnet을 만들고, 각 리소스를 subnet에 배치하고, 각 리소스끼리의 연결(가령 ALB가 웹 서버 리소스를 호출하도록 하기 등등)이 필요한 것 같다.... 아무래도 IaC는 다음으로 미루고, VPC를 직접 설정해보는 것부터 해 보면 좋겠다. 

 

💻 aws vpc 설정하기

aws의 여러 서비스들 중 'vpc'를 누르고 'vpc 생성'을 누르면 다음과 같은 화면이 나온다. 여기서 vpc만 설정할지, 아니면 그 외에 관련된 리소스(서브넷과 인터넷 게이트웨이 등)까지 같이 생성할지를 선택할 수 있다. 원래는 관련 리소스도 같이 생성하는 것이 편하겠으나, 여기서는 az를 최대 4개가 아닌 3개까지밖에 기본 옵션으로 제공하지 않아서 직접 생성하기로 했다. 그러므로 'vpc 등'이 아니라 'vpc만'을 선택했다. 

 

cidr도 사실 어떻게 등록해야 할지 감이 잘 오지 않았는데, 이전에 '10.0.0.0/16'으로 등록한 것이 기억나서 이 값을 그대로 따랐다. 이렇게 등록하면 약 60000개가 넘는 ip주소를 vpc에서 사용할 수 있다고 한다. 

 

이렇게 설정을 해 주니 아래와 같이 새 vpc가 등록되었다. 

 

이제는 서브넷을 생성할 차례이다. 방금 생성한 'onestep' 별칭을 가진 vpc를 대상으로 서브넷을 총 8개(public 4개, private 4개) 만들어주자. 

 

우선 vpc 선택에서는 방금 만든 vpc를 선택해주고, 아래와 비슷하게 할당하면 된다. 

 

이런 방식으로 총 가용 영역(az)의 수만큼 만들어주자. 나의 경우 리전이 ap-northeast-2라서 총 가용 영역이 4개인 관계로 4개 만들어줬다. 

 

그리고 현재는 '퍼블릭 액세스 차단' 필드가 비활성 상태이다. 현재는 퍼블릭 서브넷을 만든 거라 이게 맞다. 이걸 활성 상태로 만들어준다면 private subnet의 기능을 할 수 있겠다. 나머지 private subnet들도 모두 만들고, 해당 서브넷들에 대해서는 퍼블릭 액세스를 차단해 줘야겠다. 관련 메뉴가 한 눈에 보이지는 않아서 GPT 질의를 사용해봤다.

aws vpc를 만들고 subnet들을 만들었어. 그런데 이 subnet들을 private subnet으로 만들고 싶어. 어떻게 하면 돼?

 

방법은 서브넷에 연결된 라우팅 테이블(routing table)에서 인터넷 게이트웨이(IGW)로의 연결을 제거하는 것이었다. 그러려면 우선 vpc 안에 있는 라우팅 테이블이 어떤 서브넷들에 연결되어 있는지를 확인하고, 만약 이 중에 private subnet이 포함된다면 그 연결을 제거해주면 되겠다.

 

'vpc 대시보드'에서 '라우팅 테이블' 메뉴를 눌러서 들어가봤다. 그런데 방금 만들어 준 8개의 서브넷들이 모두 라우팅 테이블들과 연결되어 있지 않았다. 이럴 경우 퍼블릭 액세스를 차단하고 있진 않지만, IGW와 연결된 라우팅 테이블과 연결되지 않았으므로 모두가 private subnet처럼 동작하고 있었다고 이해했다. 그래서 이들 중 public이 포함된 서브넷들만 해당 라우팅 테이블과 연결해 주었다. 

 

원래는 '명시적 서브넷 연결'에 나온 서브넷이 한 개도 없었는데, 아래처럼 public 서브넷들만 추가해 주었다. 

 

그런데 또 궁금한 게 생겼다. 그러면 private 서브넷들은 기본 라우팅 테이블과만 연결되어 있다고 했다. 그러면 public 서브넷에 있는 리소스에서 private 서브넷에 있는 리소스로 연결이 필요할 때 어떻게 연결할까? 즉 public 서브넷과 private 서브넷은 현재 같은 vpc에 있다는 것 말고는 라우팅 테이블 등과 일말의 연결고리도 없는 상황인데, 이 상황에서 어떻게 가령 public subnet 01이 private subnet 01을 찾을 수 있을까?즉 이 두 서브넷은 vpc와 az가 같지만, 그 외의 접점은 없다고 이해한 상황이다. 바로 질의해봤다. 

 

결과는 vpc 내부 통신 메커니즘(이게 뭔지는 아직 모른다)을 통해 모든 vpc 내부의 리소스들은 서로 통신이 가능했다. 이 원리는 vpc 내 서브넷의 라우팅 테이블에 공통적으로 존재하는 'local'이라는 레코드 때문이란다. 찾아보니 정말 따로 명시해주지 않았음에도 기본적으로 이 하나의 레코드가 들어 있었다. 

 

이쯤 되니 슬슬 복잡해진다. 그림을 한번 그려보자. 기본값으로 IGW를 통해 외부로 연결되지 않은 subnet들은 다음과 같은 모습일 것이다. 

 

여기서 public 서브넷들은 vpc의 라우팅 테이블을 통해서 IGW(인터넷 게이트웨이)와 연결되어야 하겠다. 즉 아래의 그림과 같은 상황을 원한다. 

그러려면 위에서 public 서브넷들만 연결한 라우팅 테이블이 IGW와 연결되어 있는지를 확인해봐야 하겠다. 그런데 해당 라우팅 테이블의 '라우팅' 섹션을 확인해보니 local 네트워크밖에 없어서, 어떻게 확인해야 할지 모르겠다. 

하나의 vpc에서 사용되는 라우팅 테이블이 있다고 할 때, 이 라우팅 테이블을 인터넷 게이트웨이와 연결하고 싶어. 어떻게 하면 될까?

 

다음과 같은 단계를 거쳐야 했다. 

  1. 인터넷 게이트웨이를 (없으면) 만든다. 
  2. 1번에서 만든 IGW을 vpc와 연결한다. 
  3. 라우팅 테이블과 IGW을 연결한다. 
  4. 서브넷과 3번의 라우팅 테이블을 연결한다. (나는 이 부분을 위에서 했다.)
  5. 서브넷 설정에서 'ipv4 주소 자동할당' 옵션을 활성화한다. 왜냐하면 퍼블릭 서브넷 내의 리소스가 글로벌 ip를 가질 수 있어야 외부에서 접근할 수 있기 때문이다. 
  6. 아웃바운드/인바운드 규칙 편집을 통해 퍼블릭 서브넷으로 접근이 가능하도록 수정한다.

하나씩 해 보자. 우선 1번을 확인하기 위해서 'vpc' 탭의 '인터넷 게이트웨이'를 눌러보았다. 그랬더니 이미 만들어진 IGW 리소스가 보였다. 이걸 사용해도 되는 건지, 아니면 내가 새 vpc를 만들었으니 IGW도 새로 만들어줘야 하는건지 싶어서 질의해봤다. 

하나의 vpc당 하나의 인터넷 게이트웨이가 필요한 거야? 아니면 여러 vpc를 하나의 인터넷 게이트웨이에 연결할 수도 있는 거야?

 

GPT 피셜 vpc와 IGW는 1:1 관계라고 한다. 즉 vpc를 새로 만들었으면 igw도 새로 만들어야 하겠다. 만들어주고 vpc에 연결도 해 주었다. 원래 이 과정을 담아보려고 했는데 생각보다 너무 간단해서(30초컷) 캡처는 생략했다...

 

이제는 vpc 내의 라우팅 테이블과 IGW을 연결할 차례이다. '라우팅 테이블' 탭에 가서 연결할 라우팅 테이블을 선택한 뒤(없으면 만들어주자) '라우팅 편집' 메뉴를 눌러보자. 

 

그러면 아래와 같은 화면(처음에는 local 레코드만 있다)이 나오는데, 여기서 대상을 '0.0.0.0/0' 즉 모든 ip주소로 설정해 주고, 연결 대상을 인터넷 게이트웨이로 설정해준다. 그러면 아까 만든 igw를 선택한 것인지를 옵션으로 확인할 수 있고, 여기서 클릭을 해주면 된다. 

 

변경 사항을 저장하고 확인해보면, 기존엔 local만 있었던 라우팅 테이블 레코드에 인터넷 게이트웨이가 추가되었다. 

 

그 다음엔 라우팅 테이블과 연결된 서브넷이 이 인터넷 게이트웨이를 사용할 수 있도록 연결해줘야 한다. 그런데 이 작업은 위에서 이미 서브넷과 라우팅 테이블을 연결해 주었으므로 건너뛰자. 

 

이제는 퍼블릭 서브넷 설정에서 'ipv4 주소 자동할당' 옵션을 켜자. 그래야 퍼블릭 서브넷 안에 소속된 리소스들(가령 alb)이 ipv4 주소를 할당받을 수 있다. 아래와 같이 서브넷 편집 화면에서 체크박스를 눌러주면 된다. 

 

마지막으로 서브넷에 연결된 보안 관련 리소스의 아웃바운드/인바운드 규칙을 확인해 주자. 신기한 것은 보안 관련 리소스래서 '보안 그룹'만 있는 줄 알았더니 '네트워크 ACL'이라는 친구가 있었다. 처음 보는 녀석이었다. 

 

우선 익숙한 '보안 그룹'부터 설정해보자. 인바운드 규칙의 경우, 해당 개발 서버에서는 HTTP와 HTTPS 연결만 허용하려고 한다. 반면 아웃바운드 규칙의 경우 모든 연결을 허용해보겠다. 

 

이제 '네트워크 ACL'을 설정해 보자. 이 친구와는 초면이었기에 우선 이 녀석이 뭔지부터 알아야 하겠다. 

vpc 리소스의 '보안' 탭에 보안 그룹과 네트워크 ACL이라는 메뉴가 두 개 있는 것을 봤어. 보안 그룹과 네트워크 ACL이 각각 무엇이고 어떻게 다른지 설명해줘

 

보안 그룹과 ACL 모두가상 방화벽이라는 공통점이 있었다. 다만 세 가지의 차이점이 있었다. 

 

  1. 보안 그룹의 경우는 개별 리소스별로 정의되는 방화벽이라면, ACL은 서브넷 단위로 정의되는 방화벽이었다.
  2. 보안 그룹은 stateful 하기 때문에 인바운드 규칙이 허용하는 트래픽은 아웃바운드 규칙에서도 허용된다. 반면 ACL은 stateless 하기 때문에 인바운드 규칙이 어떤 트래픽을 허용하더라도 아웃바운드 규칙에서 허용하려면 별도로 설정을 해 주어야 했다. 
  3. 보안 그룹은 특정 트래픽을 '허용'하는 액션만 가능하지만, ACL은 특정 트래픽을 '제한'하는 액션도 가능했다.

 

왜 이게 필요한가 생각해 봤더니, 서브넷은 아무래도 여러 리소스들이 소속되어 있는 네트워크이다보니 개별적으로 보안그룹을 설정하는것도 물론 가능하겠지만 제어 수준을 전반적으로 관리하기 위해서는 네트워크 ACL이 훨씬 편리하겠다는 생각을 했다. 

 

아무튼 네트워크 ACL은 이런 녀석이었다. 이제 여기서 인터넷의 모든 트래픽을 막고 있는지만 확인해보면 되겠다. '보안' 탭의 '네트워크 ACL'을 눌러 확인해보니 다행히 서브넷 단위에서는 모든 트래픽을 허용하고 있었다. 

 

✅ 궁금한 점

  1. VPC와 IGW가 1:1 관계여야 하는 이유는 무엇일까?
  2. IGW의 정확한 정의는 무엇이고, IGW는 왜 필요할까?
  3. IGW로 연결한다는 게 구체적으로 무슨 의미일까?
  4. vpc 내부 통신의 원리가 궁금하다.

💻 aws route53 module 선언하기

vpc를 만들었으면 이제 route53 호스팅 리소스를 사용해봐야 되겠다. 그래야 다른 aws 리소스들을 vpc 내부에서만이 아니라 외부에서도 퍼블릭하게 사용할 수 있으니 말이다. 어제의 글에서와 같은 문서를 참고하여 나온 예제 코드를 보면서, GPT에게도 질의를 던져 보았다. 

aws route53 리소스를 사용해서 stepby.one이라는 도메인을 호스팅하는 코드를 작성해줘. 중간중간 필요한 값이 있다면 임의로 할당하고 주석으로 임의로 할당했다고만 표현해줘

 

그러다가 문득 내가 잘 하고 있는 게 맞나? 라는 의문이 들었다. 일단 냅다 코드를 입력하는 게 맞을까 싶어서 아래와 같은 명령어를 실행해봤다. 

terraform init
terraform apply

 

그런데 변하는 것이 없었다. 'No changes'란다. 공식문서를 찾아보는 접근 자체는 좋았는데, 그러다보니 실제로 어떤 코드를 입력해야 적용되는지에 대한 이해는 부족했던 것 같다. 'terraform vpc 등록'이라고 검색해서 찾은 블로그를 보니 실제 사용되는 코드에서는 직접 module을 정의할 게 아니라 resource를 가져오는 방식이었다. 

 

물론 당연히 직접 module을 만들고 그걸 사용해서도 terraform을 통해 aws 리소스를 만들 수 있었겠으나, 나에게는 아직 terraform에 대해서는 잘 모르기 때문에 코드를 보고 하나하나 따라서 입력하는 게 더 맞겠다고 판단했다. 그래서 이 코드를 그대로 가져와 보았다. 

provider "aws" {
  access_key = var.aws_access_key
  secret_key = var.aws_secret_key
  region     = var.aws_region
}

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"

  tags = {
    Name = "terraform-101"
  }
}

 

그리고 이 블로그에서 알게 된 새로운 사실인데, terraform apply 명령어를 하기 전 terraform plan 명령어를 통해 미리 어떤 리소스가 만들어질지를 볼 수 있다고 한다. 마치 django에서 makemigrations와 migrate의 차이인 것 같았다. 그래서 이 명령어도 한번 테스트 해 봤다. 

terraform plan

 

이번에는 terraform을 통해 aws vpc 리소스가 제대로 추가될 모양이다. 나머지 부분은 내일 이어서 해 보자!

 오늘 배운 것

어제 배운 것을 정리해보면, 우선 AWS provider를 선언한 다음에 VPC, Route53, VPC 보안그룹, 서브넷, EC2 spot 인스턴스 순서대로 생성하면 된다고 이해했다. (물론 아직 RDS, ECS, ECR 등의 리소스는 생성하지 않아서 추가적인 정보가 필요하다.)

 

우선 AWS provider부터 정의해보자. 'terraform aws provider'라고 검색하면 나오는 문서의 코드를 가져와서 변형해봤다. 여기서부터는 vscode 에디터에 입력하면서 해 보자. 다만 access_key와 secret_key 값은 github에 올라가면 안 되는 값이므로 aws의 secrets manager에서 가져오도록 작성해야 하겠다. 

 

💻 aws provider 선언하기

우선 variables.tf 파일을 별도로 만들어서 해당 파일 안에다가 사용할 변수 값들을 집어넣어 주자. 

variable "aws_access_key" {
    type = "string"
}
variable "aws_secret_key" {
    type = "string"
}
variable "aws_region" {
    type = "string"
}

 

그리고 terraform.tfvars 파일을 만들어서 해당 variable의 값으로 사용될 실제 값들을 넣어주자. 

aws_access_key = "your-access-key-id"
aws_secret_key = "your-secret-key"
aws_region     = "us-east-1"

 

마지막으로 main.tf 파일에다가 provider를 선언하는 로직을 만들어주자. 이렇게 하면 provider(일종의 plugin)를 사용해서 terraform에서 aws 서비스의 리소스들을 사용할 수 있다. 

provider "aws" {
  access_key = var.aws_access_key
  secret_key = var.aws_secret_key
  region     = var.aws_region
}

 

💻 aws vpc module 선언하기

다음으로는 퍼블릭 DNS와는 독립적인, aws 내부에서 동작하는 private network라고 할 수 있는 vpc를 정의하는 작업이 우선이겠다. rds도, ecr이나 ecs, ec2도 vpc 내부에서 선언되기 때문이다. 'aws vpc'라고 검색하면 나오는 문서를 참고해서 코드를 일부 바꿔 보았다. 참고로 해당 코드는 modules 디렉토리 안에 vpc.tf라는 파일에 따로 저장해 주었다. 

module "vpc" {
  source = "terraform-aws-modules/vpc/aws"

  name = "vpc_onestep"
  cidr = "10.0.0.0/16"

  azs             = ["ap-northeast-2a", "ap-northeast-2b", "ap-northeast-2c", "ap-northeast-2d"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24", "10.0.4.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24", "10.0.104.0/24"]

  enable_nat_gateway = true
  enable_vpn_gateway = true

  tags = {
    Terraform = "true"
    Environment = "dev"
  }
}

 

이게 정확히 무슨 의미일까? 하나씩 봐 보자. 

 

우선 cidr 변수의 의미가 궁금했다. 

해당 [코드]에서 cidr 변수가 무엇을 의미하는지 설명해줘

 

cidr 변수는 예전 네트워크 시간에서 배웠다시피 Classless Inter Domain Routing의 약자로, 이전에 주소 공간을 A, B, C, D, E라는 클래스로 나눠서 정의하던 방식 대신 클래스로 구분하지 않으면서 IP 주소를 할당하는 새로운 방식이었다. 다만 여기서의 cidr 변수는 VPC 내부의 주소를 할당하는 방식을 나타냈다. 

 

그리고 이후에 정의할 subnet(서브넷)들은 이 cidr에서 정의한 블록 내부의 주소 공간을 나눠서 사용하게 된단다. public subnet과 private subnet 모두 마찬가지였다. 그리고 여기서 az(available zone, 가용 영역)s, private_subnets, public_subnets의 length는 모두 같아야 하며, 각 subnet들의 i번째 원소에 해당하는 cidr 주소 블록이 i번째 az에 할당되는 주소 블록이 된다고 이해했다. 

 

그런데 왜 aws에서 vpc를 설정할 때 이런 정보들이 필요한 것일까?

aws의 vpc를 설정할 때 왜 az(availability zone), private_subnet, public_subnet이라는 개념이 필요할까? 각각의 변수들이 어떤 의미이고 서로 어떻게 연결되는지를 설명해줘

 

AZ(가용 영역)가 필요한 이유는 고가용성(한 AZ에서 장애가 발생해도 서비스를 정상적으로 이용할 수 있도록 함)과 재해 복구를 위해서라고 한다. 가용 영역은 한 리전에 여러 개가 있다고 한다. 예를 들면 eu-west-1이라는 리전(region)에는 총 3개(eu-west-1a, eu-west-1b, eu-west-1c)의 가용 영역(AZ)이 있다. 그래서 하나의 리전에 있는 하나의 가용 영역에서 장애가 발생해도 나머지 두 개의 가용 영역을 통해 서비스는 정상적으로 운영된다. 

 

그리고 하나의 AZ에는 여러 개의 서브넷이 포함될 수 있다. 즉 내가 이해하기로는 하나의 VPC 안에는 여러 개의 가용 영역이 포함될 수 있고, 하나의 가용 영역 안에는 여러 개의 서브넷(public, private 서브넷이 모두 포함됨)이 포함될 수 있다고 이해했다. 질의를 통해 이 이해가 맞는지를 점검해 보고 넘어가자. 

내가 이해하기로는 '하나의 VPC 안에는 여러 개의 가용 영역(AZ)이 포함될 수 있고, 하나의 가용 영역 안에는 여러 개의 서브넷(private, public 모두 포함)이 포함될 수 있다'고 이해했어. 이 이해가 맞으면 맞다고, 틀리면 틀리다고 말해주고 모르면 검색해서 정확히 알려줘

 

GPT 피셜로는 맞다고 한다. 그리고 하나의 VPC는 하나의 리전 내에서만 존재할 수 있다고 한다.

 

 오늘 배운 것

저번 포스트에서 IaC를 통해 기존 소마 계정의 AWS 설정을 가져오는 데에는 실패했다. 그런데 다르게 생각해보면 IaC 도구인 terraform을 통해서 코드로 여전히 새 설정을 세팅하는 건 가능하지 않을까? 그렇다면 이 복잡한 과정을 코드로 풀어본다면 문제가 아주 조금은 더 간단해질 수도 있다는 희망을 가져보았다. 

 

그리고 두 가지의 질의도 해 보았다. 

  1. terraform 파일은 버전 관리를 어떻게 할까? 일반 코드와 똑같이 Github에다 올려놓으면 되려나
  2. 만약 그렇다면 terraform 파일에 들어가는 환경변수들은 또 어떻게 관리하면 좋을까

1번은 내가 생각하는 것처럼 github에서 버전 관리를 하는 경우도 많았다. 그리고 2번의 경우는 코드에서 사용하는 환경변수 관리와 똑같았다. 나는 주로 환경변수 관리를 위해서 AWS Secrets Manager를 이용하는데 그 작업을 해 주면 되겠다. 

 

그런데 궁금한 점이 또 있다. AWS의 ECR, ECS, EC2 인스턴스를 terraform에서 새로 생성한다고 가정해 보자. 그러려면 인스턴스가 아직 생성되지 않았으므로 인스턴스의 ID 값 등이 없을 것이다. 어떻게 생성할까? 검색하다 terraform 공식 사이트의 ec2 생성 가이드를 발견하고 읽어보았다. 여러 EC2 인스턴스 중에서도 나는 비용이 저렴한 spot 인스턴스를 사용할 것이라서 관련 코드를 복사해 주었다. 

module "ec2_instance" {
  source  = "terraform-aws-modules/ec2-instance/aws"

  name = "spot-instance"

  create_spot_instance = true
  spot_price           = "0.60"
  spot_type            = "persistent"

  instance_type          = "t2.micro"
  key_name               = "user1"
  monitoring             = true
  vpc_security_group_ids = ["sg-12345678"]
  subnet_id              = "subnet-eddcdzz4"

  tags = {
    Terraform   = "true"
    Environment = "dev"
  }
}

 

그런데 코드를 복사하고 보니 EC2 인스턴스 관련 정보를 정의하기 위해서는 가령 subnet id나 vpc 보안그룹 id 등의 추가 정보가 필요했다. 다른 정보에 의존하고 있었다.

 

그러면 subnet id랑 vpc 보안그룹 id는 또 어떻게 생성하나. 공식 사이트 검색창에 'aws subnet'을 입력하니 또 뭔가가 나왔다. 이번엔 이 코드를 사용해 보자. 

resource "aws_subnet" "main" {
  vpc_id     = aws_vpc.main.id
  cidr_block = "10.0.1.0/24"

  tags = {
    Name = "Main"
  }
}

 

vpc 보안그룹도 검색해 보았다. 'aws security group'을 검색해보니 또 다른 뭔가가 나왔다. 이 코드도 일단 복붙해 보자. 이 코드는 그나마 좀 익숙한 감이 있다. 

resource "aws_security_group" "allow_tls" {
  name        = "allow_tls"
  description = "Allow TLS inbound traffic and all outbound traffic"
  vpc_id      = aws_vpc.main.id

  tags = {
    Name = "allow_tls"
  }
}

resource "aws_vpc_security_group_ingress_rule" "allow_tls_ipv4" {
  security_group_id = aws_security_group.allow_tls.id
  cidr_ipv4         = aws_vpc.main.cidr_block
  from_port         = 443
  ip_protocol       = "tcp"
  to_port           = 443
}

resource "aws_vpc_security_group_ingress_rule" "allow_tls_ipv6" {
  security_group_id = aws_security_group.allow_tls.id
  cidr_ipv6         = aws_vpc.main.ipv6_cidr_block
  from_port         = 443
  ip_protocol       = "tcp"
  to_port           = 443
}

resource "aws_vpc_security_group_egress_rule" "allow_all_traffic_ipv4" {
  security_group_id = aws_security_group.allow_tls.id
  cidr_ipv4         = "0.0.0.0/0"
  ip_protocol       = "-1" # semantically equivalent to all ports
}

resource "aws_vpc_security_group_egress_rule" "allow_all_traffic_ipv6" {
  security_group_id = aws_security_group.allow_tls.id
  cidr_ipv6         = "::/0"
  ip_protocol       = "-1" # semantically equivalent to all ports
}

 

vpc security group 리소스를 정의하기 위해서 더 이상 필요한 정보가 없을까? 잘 모르겠다. GPT에게 질의를 해 보자. 

terraform을 사용해서 해당 [링크]를 보고 AWS의 VPC 보안그룹을 설정하려고 해. 위의 코드를 가져와서 사용하려면 추가로 더 필요한 정보가 있을까? 있으면 있다고 없으면 없다고 말해주고, 모르면 검색해서 알려줘

 

GPT 피셜 'VPC'의 id가 필요하단다. 'vpc'라고 검색하니 또 다른 module 문서가 나왔다. 일단 기본 코드를 냅다 가져와 보자. 

module "vpc" {
  source = "terraform-aws-modules/vpc/aws"

  name = "my-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["eu-west-1a", "eu-west-1b", "eu-west-1c"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]

  enable_nat_gateway = true
  enable_vpn_gateway = true

  tags = {
    Terraform = "true"
    Environment = "dev"
  }
}

 

소스 코드 상 해당 코드를 작성하기 위해서 더 이상 다른 정보가 필요하지는 않아 보인다. 다만 의문이 생긴다. 아직 Route53과 같은 호스팅 서비스를 이용해서 IP주소를 할당한 것도 아닌데 어떻게 알아서 CIDR 주소를 할당할 수 있을까? 이 역시 질의로 물어보았다. 

[코드]를 보고 든 [의문점]에 대해서 해답을 알려줘

 

여기서 정의한 cidr, private_subnets, public_subnets 등의 값은 VPC와 관련된 것으로, 퍼블릭 DNS와는 관계 없는 주소였다. VPC는 AWS의 내부 네트워크 구조이기 때문이다. 애초의 VPC는 Virtual Private Cloud의 약자였다. AWS 내부의 주소 체계이며, 퍼블릭 주소 체계와는 관련 없는 별도의 주소 체계여서 호스팅 서비스와 연결하지 않고 이용이 가능했던 것이다. 

 

그렇다면 두 가지 의문이 생긴다. 

  1. 이 상황에서 Route53과 같은 호스팅 서비스와는 어떻게 연결할 수 있을까?
  2. 호스팅 서비스와 연결하지 않는다면 VPC 내에서만 서비스가 동작한다는 것인데, 어떻게 이게 가능할까?

이 역시 질의를 통해 물어보았다. 1번 질문은 웹사이트에서 찾아보면 되니 2번에 대해서만 일단 물어봤다. 

그렇다면 VPC는 DNS와는 별도의 주소 체계인 것이고 private network라고 이해했어. 그렇다면 만약 Route53과 같은 별도의 호스팅 서비스를 정의하지 않고 VPC 내부에서만 서비스가 동작하게 한다면, 어떻게 해당 서비스에 접속할 수 있어?

 

같은 VPC 내부에서만 접속이 가능하다고 한다. 복잡한 아키텍처나 보안 수준이 높은 아키텍처의 경우는 이러한 방식으로 접속이 이뤄질 수 있겠다. 우선은 우리 서비스의 일은 아니었다. 호스팅 서비스와 연결해서 공공 네트워크에서도 접속이 가능해야 했다. 'aws route53'을 입력하니 역시나 또 다른 module이 정의되어 있었다. 

 

이쯤 되니 궁금한 것이 생겼다. 공식문서 사이트에서 검색할 때 provider랑 module별로 검색결과가 구분되어 나오는데, provider와 module의 정확한 정의는 뭘까?

 

우선 module 공식문서에서는 module을 '같이 사용되는 여러 리소스들을 담아두는 container'라고 정의했다. 그에 비해 provider의 정의는 거의 연관이 없었다. 이전에도 한번 찾아봤던 것 같은데, terraform이 여러 클라우드 서비스 제공자나 SaaS 제공 업체들의 리소스를 잘 가져오고 다루기 위해 사용하는 플러그인이었다. 

 

그렇다면 providers와 modules 중 무엇을 클릭해야 할지 헷갈린다면, 지금 찾아보려는 내용이 아예 'AWS와 terraform 연결하기'처럼 독립적인 개별 서비스와 terraform을 연결하려는 활동인지 아닌지를 생각해보면 되겠다. AWS를 직접 terraform과 연결하기 위해서 맨 처음에 aws provider를 선언하는 것 외에는 내가 aws의 리소스들만 사용하는 이상은 다른 provider를 선언할 일이 없을 것 같다. 

 

아무튼 이 module의 코드도 가져와 보자. 여기서는 zones와 records라는 두 개의 모듈이 있었다.

module "zones" {
  source  = "terraform-aws-modules/route53/aws//modules/zones"
  version = "~> 3.0"

  zones = {
    "terraform-aws-modules-example.com" = {
      comment = "terraform-aws-modules-examples.com (production)"
      tags = {
        env = "production"
      }
    }

    "myapp.com" = {
      comment = "myapp.com"
    }
  }

  tags = {
    ManagedBy = "Terraform"
  }
}

module "records" {
  source  = "terraform-aws-modules/route53/aws//modules/records"
  version = "~> 3.0"

  zone_name = keys(module.zones.route53_zone_zone_id)[0]

  records = [
    {
      name    = "apigateway1"
      type    = "A"
      alias   = {
        name    = "d-10qxlbvagl.execute-api.eu-west-1.amazonaws.com"
        zone_id = "ZLY8HYME6SFAD"
      }
    },
    {
      name    = ""
      type    = "A"
      ttl     = 3600
      records = [
        "10.10.10.10",
      ]
    },
  ]

  depends_on = [module.zones]
}

 

records는 이전에 멘토님과 같이 route53을 연결했을 때 보았던 그 호스팅 레코드가 맞는 것 같았다. 다만 zones은 무엇인지 잘 모르겠다. 일단 질의를 해 봤다. 

[코드]에서 module별로 분리되는 'zones'와 'records'가 어떤 것인지 알려줘

 

zones는 AWS Route53에 DNS hosting zone을 생성하는 데 사용된다고 한다. 여기서부터는 VPC 내부에서만 사용되는 것이 아니라 VPC 내부에 정의된 여러 리소스들이 DNS와 연결되는 지점인 것이라고 이해했다. 그리고 이 zone은 해당 도메인에 대한 모든 record들의 컨테이너 역할을 한단다. 

 

 궁금한 점

  1. VPC gateway와 NAT gateway의 정의가 각각 무엇일까?
  2. terraform에서 provider와 module은 각각 무슨 개념이고 무슨 차이가 있을까?

 

4월부터 지금까지 오랜 시간이 흘렀다. 12월을 조금 넘기고 있는 시점이라 싱숭생숭할 때 마침 후기를 적으면 좋을 것 같아 글을 올려본다. 
 

✅ 개인적인 회고

소마에서 얻어갈 수 있는 것은 '사람'이라는 말을 많이 들었다. 나도 크게 다르지 않은데 다만 얻을 수 있는 것은 크게 두 가지가 있다고 생각한다. 첫 번째는 같은 팀원들과 멘토님들 및 오며가며 알고 지내던 연수생분들을 모두 포함한 열정 있는 좋은 사람들, 두 번째는 구르고 엎어지는 등 수많은 서사가 쌓인 완벽하지 않은 프로젝트이다. 
 
나는 인증을 하였냐면 그건 아니다. 창업을 하지도 않았고, 막바지에 취업에 성공한 케이스이다. 실제로도 우리 팀은 창업 or 취업 중에서는 취업을 목적으로 한 팀이었다. 소마를 수료하면서 참 행운이고 다행이었다고 생각했던 점들이 있고, '그럼에도 이건 더 잘 해볼걸'이라는 후회가 남는 점도 있다. 뿌듯함, 아쉬움, 시원섭섭함 등등 여러 감정이 교차하는 만큼 위에 언급한 두 가지에 대해서 적어보고자 한다. 
 

🍀 열정 있는 좋은 사람들 (Special Thanks)

우선 프로젝트를 끝까지 마쳤다는 점이 너무 다행이고 운이 좋았다고 생각했다. 이는 물론 나의 노력도 있지만 팀원들과 멘토님들이 없었더라면 매우 어렵지 않았을까 싶다. 개발 프로젝트를 하면서도 이렇게 긴 기간동안 프로젝트를 해본 적이 없었다. 그래서 열심히 하고자 하는, 의지를 가진 사람들과 팀을 함께하는 것이 얼마나 중요하고 또 어려운 일인지를 실감했다. 
 
먼저 우리 팀원들에게 고마움의 말을 전하고 싶다. 이런 긴 프로젝트에서 팀장을 맡아본 적은 처음이라 분명히 미숙한 점도 많았을 것이다. 그래도 잘 믿고 따라와 줘서 너무 고맙다. 덕분에 팀장은 단순히 일을 잘해야만 할 수 있는 것이 아니라, 팀원들과 좋은 관계를 유지하면서도 어떻게 하면 팀의 성과를 끌어올릴 수 있을지를 둘 다 고민해야 하는 영역임을 알 수 있었다. 그리고 사실 나는 일할 때 그렇게 분위기를 잘 풀어주는 성격은 아니라서, 아마 나와 같은 사람들 세 명이 모였다면 일은 어찌저찌 잘 해낼 수 있었겠으나 팀 분위기가 엄청 화기애애할 것 같지는 않았다. 회의도 하면서 중간중간 얘기를 조잘조잘 해 준 팀원들 덕분에 분위기가 더 좋았던 것 같아서 고맙다. 실제로 이런 분위기라 다른 연수생 분은 우리 팀을 '동물의 숲 주민들' 같다고 해주셨고, 우리 멘토님은 '어떻게 이렇게 무해한 사람들만 모으셨나요'라고도 해주셨다...ㅋㅋㅋㅋㅋㅋ 암튼 그만큼 화기애애하게 지냈던 기억이 많다. 
 
그 다음은 우리 멘토님들이다! 사실 4월에는 팀원 매칭 다음이 바로 멘토 매칭으로 이어져서 스케줄이 정말 빡셌다. 덕분에 파워 J인 나도 멘토 세 분을 각각 어떤 포지션으로 모실지에 대해서 청사진을 그리지는 못했고, 최대한 여러 분을 만나뵈면서 '이분이다..!' 라는 느낌이 드는 분을 모시려고 했다. 이름을 언급하면 특정이 될 수 있으니 멘토님들의 역할과 특징으로 언급해 보겠다.
 
헤드 멘토님은 항상 일관된 조언을 주셨고, 나는 이분 덕분에 개발자로써 구체적인 내 진로를 고민해볼 수 있었다. 취업만 하면 다가 아니구나, 완벽한 개발과 완벽한 커리어라는 것은 없구나, 일단 해보고 뭔가를 깨닫고 고치는 거구나, 라는 깨달음을 이분 덕분에 얻었고 여전히 체화 중이다. 곰돌이 멘토님은 AI와 백엔드 멘토님이셨는데 프론트 디버깅까지도 하시는 능력자셨다. 항상 우리가 어떤 문제를 말씀드려도(심지어 프론트 코드를 냅다 보여드린 적도 있다...) 뚝딱뚝딱 솔루션을 같이 찾아주셨던 점이 너무 감사했고, 스타트업의 CEO이신데도 항상 편안하게 우리를 대해 주셨다. 이분 덕분에 '개발자는 나중에 어떤 직급을 맡게 되어도 개발을 할 수 있어야 하고, 문제를 해결할 수 있어야 하겠구나'라는 깨달음을 얻었다. 마지막으로 프론트 멘토님은 항상 열정 있게 우리를 대해주시고, 회사 업무를 하면서 사이드 프로젝트도 하시면서 우리 멘토링도 해 주시는 시간의 마법사셨다. 프론트 지식을 따로 정리해서 멘토링도 해 주시고, vscode로 직접 라이브코딩을 하면서 문제에 대해 조언을 주셨어서 온오프라인에 관계 없이 뵐 때마다 든든했던 기억이 있다. 이분 덕분에 '나도 저렇게 취업한 이후에도 계속해서 뭔가를 해 나가는 열정 있는 개발자가 되고 싶고, 꾸준히 회고하고 지식을 공유하는 개발자가 되고 싶다'는 새로운 지향점을 얻었다. 
 
그 다음엔 소마에서 오며가며 뵈었던 모든 연수생들이다. 나는 그렇게 발이 넓은 편은 아니라 엄청 많은 분들과 알며 지내지는 못했다. 아마 6월까지 학교를 다니고 7월부터 센터를 많이 나와서 그렇게 된 부분도 컸던 것 같다. 그럼에도 몇 분들과는 오며가며 뵙고 종종 안부를 물으며 지냈다. 소마 센터에 꾸준히 나오시는 분들은 대부분 이 프로젝트에 열정이 있고, 개발이나 기획에도 열심히 참여하시는 분들이 많았다. 덕분에 잠시 열정을 잃어버렸을 때 다른 분들과도 얘기를 나누며 그 열정을 조금 더 받아갈 수 있었다. 오며가며 날 보신 분들이 계시다면 덕분에 열정 있고 재밌는 소마 생활을 할 수 있었어서 감사하다는 말씀을 전하고 싶다. 
 

🎓 완벽하지 않은 프로젝트

분명 소마를 붙고 활동을 시작하기 전에는 기깔나는 프로젝트를 만들어서 내 포트폴리오로 삼아야겠다는 취준생의 세속적인 생각이 있었다. 지금도 그런 생각이 아예 없다고는 못 하겠는데, 이 완벽하지 않은 나의 프로젝트를 만나면서 이 생각에 하나의 전환점이 생겼다. 완벽한 것은 앞으로도 없을 것이고, 문제를 마주하고 고치는 과정 자체를 즐겨야 내가 개발자로서 성취감 있고 재밌게 살아갈 수 있겠다는 생각이었다. 
 
이 프로젝트에 나름의 노력을 다했었다. 그리고 최고라고는 못 하겠지만 매 순간 나름대로 생각하는 최선이라는 선택을 했었다. 당연하게도 이 프로젝트는 완벽하지 않았다. 사용자를 거의 모으지 못했고, 최초에 기획했던 것을 전부 다 구현하지는 못했으며, 성능 이슈도 있다. 그럼에도 불구하고 나한테 가장 기억에 남는 프로젝트임은 확실하다. 모든 것을 결정하는 과정에 나름의 이유와 과정이 있었기 때문이다. 
 
가령 우리 프로젝트에는 동기화 기능이 있다. 사용자가 태블릿이나 스마트폰 등 여러 기기에서 앱을 동시에 사용할 때 동기화가 되도록 구현해 놓았다. 다만 '동기화'하면 생각나는 '웹소켓'이나 '롱 폴링(long polling)'을 쓰지는 않았다. 우리는 백그라운드 푸시 알림으로 동기화 기능을 구현했다. 이유는 명확했다. 최대한 빠르게 구현하기 위해서였다. 
 
당시 프로젝트의 사용자가 없었고, 중요한 것은 일단 빠르게 구현해서 배포를 하는 것이었다. 물론 이 방법을 선택했음에도 다른 문제들이 있어서 결과적으로 빠른 배포가 되지는 않았었다. 그리고 웹소켓과 롱 폴링이라는 방법도 완벽한 방법은 아니었다. 단지 문제를 해결하는 하나의 방법일 뿐이었다. 지금은 사용자가 없으니 가장 간단한 방법으로 동기화를 구현해 볼 뿐이었다. 당연히 이 방법도 단점이 있었다. 우선은 푸시 알림의 본 목적과 다른 목적으로 동기화를 위해 사용하므로 취지에 맞지 않는다는 문제가 있었고, 백그라운드 푸시 알람이 너무 빈번하게 오게 되면 FCM 서버 등의 다른 곳에 부하가 될 수도 있었다. 그러면 그 문제가 주는 불편함이 현재의 편리함보다 더 커졌을 때 구현 방법을 바꾸면 되는 일이었다. 
 
이런 류의 여러 문제들을 마주하면서 어려운 기술을 쓰는 게 중요한 게 아니고, 어떤 문제를 풀기 위해서 어떤 기술이나 접근을 썼고, 그걸 왜 사용하였는지가 더 중요함을 깨달았다. 지금까지 나는 내가 어렵고 복잡한 기술을 쓰지 못해서 개발을 잘 못 한다고 생각했고, 더 잘 하기 위해서는 그런 기술을 알아야만 한다고 생각했었다. 그런데 그건 아니었다. 오히려 내가 자신이 없었던 이유는 매 순간 내가 무엇을 문제라고 생각했고, 그래서 무엇을 해결책으로 생각했으며, 왜 그걸 선택했고 어떤 장단점이 있는지가 명확하지 않아서였다. 물론 이 과정조차 완벽하진 않은데, 그럼에도 그런 '나만의 생각'이 없어서 내가 더 확신이 없었던 것이라고 생각한다. 개발자도 결국은 문제를 해결하는 사람의 부분집합이었다. 그 당연한 사실을 소마를 통해 조금이나마 더 깨달을 수 있었다. 
 

➡️ What's next (Solopreneur)

1월부터는 어엿한 사회인 개발자가 되어 다른 곳에서 또 개발을 이어간다. 여기서 배운 것들을 잊지 않고, 현업에서 이 마음가짐을 갖고 문제를 해결하는 개발자가 되고 싶다. 물론 나는 주니어 개발자가 맞지만, 스스로를 주니어라고 한정짓지는 말아야 하겠다. 스스로에게 주어지는 일들은 잘 해내려고 노력하고 모르면 모르는 대로 고민하고 도움을 요청하는 것도 좋지만, '나는 주니어니까 이것까지만 하면 되겠지'라는 마음가짐을 경계하자. 
 
최근에 참여한 다른 특강에서 Solopreneur, 1인 창업가라는 단어를 들었는데 인상깊었다. 'AI가 개발자를 대체할까'와 비슷한 주제의 특강에서 내가 이런 질문을 했었고, 여기에 대한 강연자분의 답변이 '1인 창업가가 되어라'는 말이었다. 

주니어 개발자는 상대적으로 대체되기 쉽고, CTO나 테크 리드는 직접 문제를 정의하는 사람이라 상대적으로 대체되기 어려울 것 같다는 인상을 받았습니다. 어떻게 하면 주니어 개발자인 상황에서도 스스로 문제를 정의하는 개발자로써 기능할 수 있을까요? 일단 생각되는 방법은 주니어 개발자인 상황에서도 꾸준한 성찰과 회고를 통해 문제를 직접 정의하는 과정을 연습해보는 것인데, 혹시 또 다른 좋은 방법이 있을지 궁금합니다.

 
정말로 1인 창업을 하라는 말씀이었을 수도 있지만, 모두가 회사를 안 다닐 수는 없지 않을까? 나는 이 말을 이렇게 해석했다. '아직 스스로가 회사에서는 주니어 레벨이어도, 혼자 사이드 프로젝트를 하면서나, 주니어로 임하면서도 마치 창업을 하고 있다고 생각하면 그 깊이를 끝까지 달려볼 수 있다.' 스스로의 역할을 주니어 개발자라고 한정짓지 말고, 만약 사이드 프로젝트를 한다면 이 사이드 프로젝트에서만큼은 내가 기획자이고, 개발자이고, 동시에 마케터도 되는 것이다. 결국 중요한 것은 문제를 해결하는 것이니 말이다. 개발자의 본질도 결국은 문제 해결이라고 느꼈다. 
 
그러나 이 때문에 현업을 소홀히 하는 것 또한 있어서는 안 될 일이었다. 어떻게 하면 밸런스를 맞출 수 있을까? 멘토님께 여쭤봤더니 일단 한 회사에 몸담고 있는 상황에서는 그 조직에서 내가 맡은 일을 잘, 효율적으로 해내는 것이 목표가 되어야 한다고 하셨다. 그리고 그러한 일을 잘 하면서도 여유 시간이 남는다면, 그때 시도해 보는 것은 괜찮을 것 같다는 조언을 해주셨다. 그래서 일단은 이렇게 해보려고 한다. 아마 이렇게 호기롭게 말을 했어도 이후 몇 달간은 신입으로써 적응하는 데 시간을 많이 쓸 것 같고, 그 영역도 분명 내가 생각하는 것보다 훨씬 더 넓고 깊은 세계일 것이다. 그때 가서 내가 너무 무모했나 싶은 허탈한 마음의 자책성 회고가 올라올 수도 있겠다... 그래도 일단은 이 마음을 잊지 말자. 
 

+ Recent posts