IT/GoLang

첫번째 글 - Hello World on API Gateway

Aaron's papa 2021. 9. 22. 22:29
반응형

Go 언어는 Go 루틴, 고계함수, Interface 등과 같이 Go 언어를 Go 언어 답게 만들어 주는 몇가지 기능들이 있습니다. 사실 이런 것들이 Go 언어를 어렵고 복잡하게 만들기도 하지만 그만큼 강력하게 만들어 주기도 하죠. 하지만 이런 특징적인 기능들 없이 기본적인 기능들만 잘 활용해도 Go 언어를 이용해서 다양한 애플리케이션을 만들 수 있습니다. 특히 AWS Lambda 서비스와 함께 동작하면 간단하면서도 확장성 높은 API 서버 혹은 배치 작업들을 만들어 낼 수 있죠.

이번 시리즈의 목표도 그렇습니다.

Go 언어를 Go 언어 답게 만들어 주는 특징적인 기능들을 빼고, 기본적인 문법들과 패턴들을 이용해서 나에게 필요한 애플리케이션을 만들어 보는 시리즈 입니다.

하나의 글 마다 목표로 하는 애플리케이션을 만들고 배포 해서 잘 동작하는지 확인하는 과정들을 통해 Go 언어에 익숙해 지고 AWS Lambda API Gateway 그리고 serverless 프레임워크에 대해서 이해 할 수 있는 글을 만들어 보려고 합니다.


Go vs Python

저는 지금까지 Python을 주로 사용 했었습니다. 다양한 라이브러리와 쉬운 문법 등을 바탕으로 간단한 작업을 위한 스크립트 작성용으로는 최적 이었습니다. 특히 DevOps 혹은 시스템 엔지니어 업무를 하는 사람들이라면 Python 기반으로 자동화 작업들을 한두번은 만들어 봤을 겁니다. 그리고 Python에는 Flask 라는 강력한 라이브러리가 있어서 필요하다면 수 분 안에 API 서버를 뚝딱뚝딱 만들어 내는 것도 가능했죠.

하지만 제 개인적으로 느꼈던 Python의 가장 큰 불편함은 바로 배포 였습니다.

내 로컬 환경에서 만든 Python 애플리케이션을 다른 환경에서 동작하게 하기 위한 배포 작업이 가장 힘들었죠. Python의 버전 문제도 있었지만, 라이브러리 종속성을 처리 하기 위한 requirements.txt 정의 등의 작업들도 배포를 어렵게 만드는 부분이기도 했습니다. 물론 virtualenv 와 같은 것을 이용해서 애플리케이션 별로 독립적인 실행 환경을 만들어 낼 순 있었지만 그것도 배포 시 virtualenv 를 위한 작업들을 해줘야 하기 때문에 복잡성을 높이긴 했습니다.

하지만 Go 언어의 경우 바이너리 빌드가 가능하기 때문에 배포를 하기 위해 종속성을 걱정해야 할 일이 없어 졌습니다. 종속성 패키징을 관리하고 배포할 때 이것들을 포함하지 않아도 되었죠.

그저 빌드를 해서 생성된 바이너리 파일을 복사하기만 하면 어떤 환경에서든지 잘 동작 하게 되었습니다. 저는 이게 Go 언어가 가진 가장 큰 장점 중 하나라고 생각 합니다.

DevOps 혹은 시스템 엔지니어들이 쉽고 빠르게 애플리케이션을 작성하고 배포할 수 있게 되어 업무 효율성을 크게 올릴 수 있게 된 특징 중 하나라고 생각 합니다.

 

그럼 본격적으로 환경 설정부터 진행해 보겠습니다.


Go 환경 설정

본 시리즈에서는 Goland를 기본 IDE로 사용해서 예제들을 진행할 예정 입니다.

Goland는 30일의 무료 사용 기간이 지나면 무조건 돈을 내고 사용해야 하는 유료화 도구이기 때문에 Goland를 사용하지 않는다면 atom 과 같은 도구를 사용하는 것을 권장 합니다.

우선 Go 언어를 설치해 보겠습니다. 맥 환경 이라면 brew 명령을 이용해 간편하게 설치할 수 있습니다.

brew update 
brew install golang

설치가 완료되면 아래와 같이 버전을 확인해 봅니다.

❯ go version
go version go1.16.2 darwin/arm64

go version go version go1.16.2 darwin/arm64

이후로 GOPATH 등의 환경변수를 설정하는 작업을 해줘야 하지만 go.mod 가 적용된 이후로는 이런 작업이 필요 없어졌습니다. 그저 Go를 설치하기만 하면 개발할 수 있는 모든 준비는 끝이 납니다. 이제 아주 간단하게 누구나 만들어 볼 만한 Hello World 를 만들어 보겠습니다.


Hello World by Golang

Goland에서 아래와 같이 hello-world-go 라는 프로젝트를 만들어 줍니다.

hello-world-go 프로젝트 생성하기

이후 라이브러리 설치 및 빌드를 위해서 go mod init 명령을 이용해 초기화 해 줍니다. go mod init 시 뒤에 사용하는 모듈의 이름을 github.com 을 이용해서 만들 수도 있습니다만, 이번 글에서는 간단하게 만들어 줍니다. Goland 하단에 있는 터미널을 열어서 입력해 주면 훨씬 편하게 만들어 줄 수 있습니다.

❯ go mod init hello-world-go
go: creating new go.mod: module hello-world-go

그리고 Goland에 보면 go.mod 파일이 생성된 것을 볼 수 있습니다. go.mod 파일을 열어 보겠습니다.

module hello-world-go

go 1.16

이제부터 우리가 만들게 될 모듈의 이름은 hello-world-go 라는 모듈이 됩니다. 이 모듈 정의는 향후 모듈 내에서 다수의 패키지를 만들때 참조하기 때문에 매우 중요 합니다. 모듈 설정까지 되었다면 Hello World 를 찍어 보겠습니다.

package main

import "fmt"

func main() {
    fmt.Println("Hello World")
}

정말 너무나도 유명한 애플리케이션이죠. Hello World 입니다. 이제 빌드 해 보겠습니다.

❯ go build ./main.go

❯ ls -al
total 4072
drwxr-xr-x@ 6 alden  staff      192 Jun  6 16:06 .
drwxr-xr-x@ 7 alden  staff      224 Jun  6 15:59 ..
drwxr-xr-x  6 alden  staff      192 Jun  6 16:05 .idea
-rw-r--r--  1 alden  staff       31 Jun  6 16:02 go.mod
-rwxr-xr-x  1 alden  staff  2076146 Jun  6 16:06 main
-rw-r--r--  1 alden  staff       72 Jun  6 16:05 main.go

main 이라는 실행 파일이 생겼습니다. 빌드의 결과물을 저장하는 옵션도 있지만 우선은 이렇게 간단하게 시작해 보겠습니다. 이제 생성된 main 실행 파일을 실행해 보겠습니다.

❯ ./main
Hello World

너무나도 당연한 결과겠지만, Hello World가 찍혔습니다. 참 쉽죠? 하지만 이번 글은 여기까지가 끝이 아닙니다. 이 간단한 걸 우리는 AWS Lambda로 올려서 API Gatway로 Hello World를 찍어 보겠습니다. 어떠세요? 가슴이 두근두근 하시나요?


serverless 프레임워크 설치하기

이번 시리즈에서 계속 사용하게 될 serverless 프레임워크를 설치해 보겠습니다. serverless 프레임워크는 AWS Lambda 함수의 배포를 손쉽게 해주는 프레임워크 입니다.

https://www.serverless.com/에서 자세한 정보를 볼 수 있습니다.
curl -o- -L https://slss.io/install | bash

혹시 m1 Mac 이라면 아래와 같이 brew로 설치 하면 됩니다.

brew install serverless

 

이제 AWS Lambda에서 실행 할 수 있도록 hello world 코드를 수정해 보겠습니다.


AWS Lambda를 위한 코드 수정

먼저 AWS Lambda를 위한 패키지들을 임포트 해 줘야 합니다. 소스 코드를 아래와 같이 변경해 줍니다.

package main

import (
    "fmt"

    "github.com/aws/aws-lambda-go/events"
)

func handler(events.APIGatewayProxyRequest) {

}

func main() {
    fmt.Println("Hello World")
}

그럼 아마도 아래와 같이 패키지 이름에 빨간색 줄이 그어질 겁니다.

패키지 임포트 에러

패키지를 임포트 한 적이 없기 때문에 발생하는 당연한 에러 입니다. 우리는 이 에러를 해결하기 위해 앞에서 go.mod를 생성 했습니다. 당황하지 말고 Goland 터미널에서 아래와 같이 go mod vendor명령을 입력해 줍니다.

❯ go mod vendor
go: finding module for package github.com/aws/aws-lambda-go/events
go: downloading github.com/aws/aws-lambda-go v1.24.0
go: found github.com/aws/aws-lambda-go/events in github.com/aws/aws-lambda-go v1.24.0

그럼 친절하게도 우리가 방금 추가한 패키지를 위한 종속성 패키지들을 같이 가져와서 설치해 줍니다. 바로 vendor 디렉터리에요. go mod vendor 라는 명령은 필요한 패키지들을 vendor 라는 디렉터리에 다운받아라 는 명령 입니다. 하지만 이 이후에도 Goland에서는 패키지에 빨간줄이 지워지지 않을 겁니다. Goland를 종료 하고 다시 실행 시켜서 소스 코드를 열어 줍니다. 그럼 이번엔 빨간줄이 없어졌을 겁니다.

아마도 Goland의 버그성 동작인지 잘 모르겠지만, 재시작 하면 그 후로는 vendor에 있는 패키지를 잘 인식 합니다.

패키지 임포트 성공

이제 본격적으로 코드를 작성해 보겠습니다. 우리가 작성하는 애플리케이션을 API Gateway로 부터 HTTP 요청을 전달 받아서 무언가를 처리하고 HTTP 응답의 형태로 돌려 줘야 합니다. 즉 아까와 같이 그냥 fmt.Println() 을 해주면 안되고 API Gateway가 응답을 해석해서 정상적인 HTTP 응답으로 내려 줄 수 있도록 해줘야 합니다. 이를 위해서 handler() 함수를 수정해 줍니다.

package main

import (
    "net/http"

    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
)

func handler(events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error){
    return events.APIGatewayProxyResponse{
        StatusCode: http.StatusOK,
        Body: "Hello World",
    }, nil
}

func main() {
    lambda.Start(handler)
}

 

중요한 코드들이 몇몇 보입니다. 아마 이 코드를 Goland에 입력하면 lambda 임포트 부분에 빨간줄이 생길 겁니다. 이는 어느 정도 시간이 지나면 Goland가 필요한 의존성을 자동으로 설치해서 해결하게 됩니다.

코드의 흐름을 따라가 보겠습니다. 애플리케이션은 Lambda로 동작하기 때문에 lambda.Start가 반드시 필요 합니다. Lambda가 애플리케이션을 시작하는 시작점이 되는 부분인데 이 부분이 없으면 애플리케이션은 Lambda에서 실행되지 않습니다. Lambda가 애플레케이션을 실행 시키면 handler() 함수를 실행하게 되고 이 때 함수의 파라미터로 API Gateway로 부터 전달 받은 HTTP 요청을 같이 넘겨 줍니다. Lambda가 실행 시키는 방법에 따라 저 파라미터들은 API Gateway 가 될 수도 있고 CloudWatch Event가 될 수도 있습니다. 이에 대해서는 이후의 예제들을 통해 천천히 살펴 보겠습니다.

handler() 함수는 API Gateway가 해석할 수 있도록 API GatewayProxyResponse 의 형태로 응답을 작성해서 돌려 줍니다. 이 형태의 응답은 AWS 리소스들을 다룰 때 보게 되는 패턴 입니다. AWS 리소스들은 대부분 이렇게 Input과 Output 형태의 구조체를 정의해 주는 패턴으로 사용 됩니다.

이제 serverless 프레임워크로 배포 해 보겠습니다. serverless.yaml 파일을 작성해 줍니다.

service: hello-world

provider:
  name: aws
  runtime: go1.x
  memorySize: 256
  region: ap-northeast-2
  timeout: 30

package:
  exclude:
  - ./**
  include:
  - ./bin/**

functions:
  helloWorld:
    handler: bin/hello-world
    events:
    - http:
        path: /
        method: GET

 

이제 go build 명령을 통해 Lambda에서 실행할 바이너리를 만들어 보겠습니다. 이 부분도 매우 중요한데요, Mac 에서 빌드 하면 당연히 Lambda에서 실행이 안됩니다. Lambda에서 실행할 수 있도록 리눅스 바이너리로 만들어 줍니다.

❯ env GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o bin/hello-world main.go

❯ ls -al ./bin
total 11008
drwxr-xr-x   3 alden  staff       96 Jun  6 16:36 .
drwxr-xr-x@ 10 alden  staff      320 Jun  6 16:36 ..
-rwxr-xr-x   1 alden  staff  5636096 Jun  6 16:36 hello-world

 

나중에 Makefile을 통해 이 부분도 간략하게 할 수 있습니다. 이제 bin/hello-world 파일도 생성 되었으니 serverless 명령으로 배포 해 보겠습니다.

❯ sls deploy
Serverless: Deprecation warning: Support for "package.include" and "package.exclude" will be removed with next major release. Please use "package.patterns" instead
            More Info: https://www.serverless.com/framework/docs/deprecations/#NEW_PACKAGE_PATTERNS
Serverless: Deprecation warning: Resolution of lambda version hashes was improved with better algorithm, which will be used in next major release.
            Switch to it now by setting "provider.lambdaHashingVersion" to "20201221"
            More Info: https://www.serverless.com/framework/docs/deprecations/#LAMBDA_HASHING_VERSION_V2
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service hello-world.zip file to S3 (2.5 MB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
..............
Serverless: Stack update finished...
Service Information
service: hello-world
stage: dev
region: ap-northeast-2
stack: hello-world-dev
resources: 10
api keys:
  None
endpoints:
  GET - https://l9idsiimc4.execute-api.ap-northeast-2.amazonaws.com/dev/
functions:
  helloWorld: hello-world-dev-helloWorld
layers:
  None

 

와.. 배포도 되었습니다. curl 명령으로 확인해 볼까요?

❯ curl https://l9idsiimc4.execute-api.ap-northeast-2.amazonaws.com/dev/
Hello World

❯ curl https://l9idsiimc4.execute-api.ap-northeast-2.amazonaws.com/dev/ Hello World

정말 멋지지 않습니까? 이렇게 우리는 Go 언어 기반으로된 API 서버를 하나 만들어 냈습니다. 이런 식으로 슬랙 웹훅을 처리하는 봇을 만들 수도 있고, ArgoCD에서 발생하는 웹훅을 받아서 자동화 하는 API 서버도 만들어 볼 수 있고 무궁무진하게 활용할 수 있습니다.


마치며

오늘은 첫번째 글이었던 만큼 Go 언어를 사용해야 하는 이유와 아주 간단한 hello world 프로그램을 만들어 봤습니다. 이후로는 좀 더 다양한 예제를 통해서 Go 언어를 Lambda와 결합해서 사용하는 방법을 하나씩 알아 보도록 하겠습니다. 고생 많으셨습니다~!

반응형