ALLSSU

[AWS CDK] Amplify Hosting + Gitlab 저장소 연동

Amplify 호스팅을 위해 Gitlab 저장소를 연동하기 위한 CDK 코드와 Gitlab Token을 관리하기 위한 Secrets Manager의 사용

2023년 1월 4일 수 00:09

Amplify Hosting에서 Secrets Manager을 통해 토큰을 관리하고 Gitlab을 연동하는 아키텍처

CDK를 통해 Gitlab에서 사용하는 토큰을 관리하고, Amplify에서 Gitlab 저장소를 연동할 수 있다.

결론부터

11월부터 Amplify Hosting에서 Next.js 12, 13을 지원하기 시작했다. Gitlab에 저장한 Next.js 13버전 코드를 Amplify에서 동작시켜보기 위해 CDK 코드를 작성했고, AWS Amplify 스택을 개발해서 Gitlab Provider을 연동했다. 연동 과정 중 AWS Secrets Manager을 통해서 Gitlab Personal Access Token을 관리하고 CDK에서 연결해서 사용하게 됐다.

Amplify 및 Amplify Hosting

AWS Amplify는 AWS 서비스들을 이용해서 풀스택 앱을 만들 수 있도록 해주는 솔루션이다. 호스팅부터 데이터베이스, 분석, AI/ML(amplify predictions)도 지원하고, Amplify CLI 명령어만 입력하면 알아서 프로비저닝하고 연결해준다. 프론트엔드 라이브러리(Javascript, Swift, Android, Flutter, React Native)까지 지원을 해주는데, Firebase와 포지션이 같은 AWS의 솔루션이다.

Amplify의 여러 가지 기능 중에서 Next.js 애플리케이션을 동작시킬 Hosting 기능을 이용했는데, Amplify Hosting 서비스Vercel처럼 리파지토리만 연결하면 Next.js 앱을 자동으로 빌드해서 실행해주는 기능이 있다. 저장소는 Gitlab Private Repository를 연동했는데, AWS Management Console이 아닌 AWS CDK에서 연동하기 위해 여러가지 시행착오를 겪게 되었다. 우선 Amplify를 CDK L2 레벨로 사용하기 위해 아직 Experimental 단계인 @aws-cdk/aws-amplify-alpha 라이브러리를 설치해서 진행했다. L1버전이 있지만 L1버전을 CDK에서 사용하는 건 Cloudformation을 단순히 JSON으로 사용하는것과 같은 난이도이고, Cloudformation 위에 멋지게 추상화 되어있는 CDK를 쓰는 매력 자체가 없어진다. (CDK Concept: L1 ~ L3)

CDK Amplify에서의 Gitlab 연동

CDK에 정의되어 있는 GitLabSourceCodeProvider 클래스를 사용하면 Amplify가 깃랩에 있는 저장소를 읽어서 소스를 가져온다. 예제가 나와있는데, owner, repository, oauthToken을 필수로 입력받도록 되어 있다.

GitLabSourceCodeProvider Example (CDK v2 / typescript)

const amplifyApp = new amplify.App(this, "MyApp", {
  sourceCodeProvider: new amplify.GitLabSourceCodeProvider({
    owner: "<user>",
    repository: "<repo>",
    oauthToken: SecretValue.secretsManager("my-gitlab-token"),
  }),
});
CDK에서 Amplify 앱을 만들고 Gitlab을 연동하는 코드 예제.

owner을 입력할 때, 프로젝트가 Gitlab 그룹에 포함되어있다면 그룹명을 적어야한다. 예를 들어 gitlab.com/mygroup/저장소네임으로 되어있다면 mygroup을 적어야하는데, allssu만 입력해놓은 다거나 숫자로 되어있는 사용자 ID를 입력하고 repository만 변경해가면서 테스트하다가 한참 시간을 보냈다. owner에 유저이름이나 ID를 입력해봤자 프로젝트를 찾지 못한다는 404 에러가 뜬다. 프로젝트를 사용자 밑에 둔다면 사용자명을 입력하면 되겠지만, 그룹 밑에 둔다면 owner는 그룹명이 들어가야 한다. 이거 때문에 한참을 해메게 되었는데, CDK 문서에는 외부 서비스라 그런지 생각보다 친절하게 적혀있진 않았다. 😭

repository의 경우 저장소 이름 그대로 적으면 된다.

마지막으로 oauthToken을 입력하기 위해 Secrets Manager을 사용하는 예제를 보여주는데, CDK에서 Secrets Manager을 만들면 깔끔하게 연동할 수 있다.

Gitlab 엑세스 토큰과 Secrets Manager 생성하기

먼저 Amplify가 사용할 Gitlab 토큰을 발급해야 한다. api 및 read_api 권한 두개를 부여해야 에러가 안 나고 Amplify에서 사용할 수 있다. api, read_api 권한이 체크되어있는 깃랩 액세스 토큰 환경설정 화면

api, read_api를 부여하면 amplify에서 저장소를 연동할 수 있다. 토큰 명이나 기간은 필요에 따라 설정하면 된다.

토큰이 발급되었으면 Secrets Manager에 등록하면 되는데, 우선 아무런 비밀키를 포함시키지 않은 Secrets Manager을 CDK로 프로비저닝했다.

Secrets Manager Stack (CDK v2 / typescript)

/**
 * /lib/secretsmanager-stack.ts
 */
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import { aws_secretsmanager as secretsmanager } from "aws-cdk-lib";

export class SecretsmanagerStack extends cdk.Stack {
  public readonly secret: secretsmanager.Secret; // 다른 스택으로 Secret을 전달시키기 위한 클래스 속성
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    this.secret = new secretsmanager.Secret(this, "Secret", {});
  }
}
보안 암호를 입력하지 않고, CDK를 통해 Secrets Manager을 프로비저닝하는 스택 코드

CDK를 통해 배포하면 보안 암호가 난수로 생성이 되는데, AWS Management Console에 직접 들어가서 Gitlab Personal Access Token을 변경했다. CDK 소스에 암호가 포함되지 않고 더욱 안전하게 관리된다.

AWS Management Console의 Secrets Manager에서 보안 암호를 변경하는 그림

CDK 소스에 토큰이 남지 않고, CDK에서 쉽게 Secrets Manager을 사용할 수 있게 된다.

Amplify 스택에서 Secrets Manager 연동하기

Amplify에서는 보안 암호를 받을 수 있도록, StackProps를 상속한 인터페이스를 생성해준다. 이렇게 하면 보안 암호 값이 포함되어있는 보안 암호 이름(식별자)도 노출하지 않고 연동할 수 있게 된다.

Amplify Stack (CDK v2 / typescript)

/**
 * /lib/amplify-stack.ts
 */
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import * as amplify from "@aws-cdk/aws-amplify-alpha";
import { SecretValue } from "aws-cdk-lib";

interface StackProps extends cdk.StackProps {
  secret: cdk.aws_secretsmanager.Secret;
}

export class AmplifyStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: StackProps) {
    super(scope, id, props);

    new amplify.App(this, "amplify", {
      appName: "MyApp",
      sourceCodeProvider: new amplify.GitLabSourceCodeProvider({
        owner: "group", // 프로젝트가 깃랩 그룹에 포함되어있다면, 그룹 이름을 적어야 한다.
        repository: "app",
        oauthToken: SecretValue.secretsManager(props.secret.secretName), // 전달받은 Secret Name
      }),
    });
  }
}

Secrets Manager 스택과 Amplify 스택은 서로 값을 주고받을 수 있게 되었고, CDK의 시작점인 /bin에서 Amplify 스택에 Secret을 전달했다.

스택 연결 및 생성 (CDK v2 / typescript)

/**
 * /bin/cdk.ts
 */
#!/usr/bin/env node
import "source-map-support/register";
import * as cdk from "aws-cdk-lib";
import { CognitoStack } from "../lib/cognito-stack";
import { AmplifyStack } from "../lib/amplify-stack";
import { SecretsmanagerStack } from "../lib/secretsmanager-stack";

const app = new cdk.App();

const { secret } = new SecretsmanagerStack(app, "SecretsmanagerStack", {
  description: "Amplify Secrets",
});

new AmplifyStack(app, "AmplifyStack", {
  secret,
  description: "Next.js 13 App Amplify",
});

정리 및 회고

  • AWS Amplify에서 Gitlab과 같은 외부 프로바이더를 사용하기 위해 CDK는 콘솔보다 시간이 많이 들어간다. (콘솔에서는 프로바이더 로그인만 하면 되기 때문에😂) 그러나, 초기 작업량이 많은 단점에 반해 장기적으로 Amplify 앱에 연동되는 백엔드 리소스들의 관리 편의성 면에서 하나의 CDK 프로젝트에 통합하는 것이 낫다고 생각되었다. 또한 Amplify 마저도 도메인이나 브랜치 관리까지 CDK로 가능하니 콘솔의 한계를 다시금 느끼게 되었다.

  • Secrets Manager이 심플해서 사용하기는 편하다고는 느꼈으나, CDK에서 굉장히 강력하다고 느껴졌다. CDK에서 생성한 Secrets Manager은 참조로 연결되기 때문에 보안 암호 이름(보안암호 값을 찾을 수 있는 key)마저 가려버릴 수 있고, CDK 소스가 노출되어도 암호 값은 노출될 일이 훨씬 적어질 것으로 생각되었다. 자주 써야지.

  • CDK는 재밌고, 쓰면 쓸수록 잘 만들었다고 생각된다. 특히나 기가 맥히게 편한 문서가 있기 때문이기도 하고, AWS 서비스들의 퀄리티와 서비스 간의 연동이 뒷받침해 줬기 때문 아닐까.