본문 바로가기
Developer Portal/Backstage

Backstage + Terraform을 통한 AWS 리소스 배포

by wlsdn3004 2024. 6. 12.
728x90
반응형

 

 

다음은 배포 Workflow에 대한 그림이다.

위 Workflow 과정을 간략하게 설명하면 다음과 같다.

  1. Backstage의 Software Template을 작성하여 Github repository에 보관한다.
  2. Software Template에는 아래와 같은 step들이 정의되어 차례로 진행된다.
    1. 미리 정의된 Software Catalog Template 파일이 입력값을 통해 수정되어 Github repository에 등록된다.
    2. Backstage의 Software Template에서 입력된 값을 사용하여 미리 정의된 Github Action 파일을 통해 runner가 실행된다.
    3. Github repository에 등록된 Software Catalog 파일을 통해 Backstage catalog에 서비스가 등록된다.
  3. runner에 의해 Terraform이 실행되며, Terraform 파일들은 AWS S3에 보관된다.
  4. Terraform 실행에 의해 AWS 리소스가 배포된다.

 

이번 글에서는 Backstage를 통해 EC2 인스턴스를 배포하는 실습을 진행해보려 한다.

 

구성 환경

  • Amazon EC2 Instance

구성 버전

  • Backstage : 1.27.0
  • EC2 : Amazon2023-6.1.59-84.139.amzn2023.x86_64

전제 조건

 

 

1. Backstage Software Template 작성


다음 Tree 구조와 같이 Github repository에 Template 파일을 보관한다.

├── content
│   ├── component-info.yaml
│   ├── index.js
│   ├── package.json
├── template.yaml

 

다음 파일들을 Github repository에 등록한다.

  • template.yaml
    • Github Action 파일이 존재하는 '{github_repo}' 부분을 적절히 기입한다.
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: create-ec2-instance
  title: Create an EC2 Instance
  description: Create an Instance to deploy your application
  tags: ['recommended']
spec:
  owner: guests
  type: service
  parameters:
    - title: EC2 Instance Creation
      required:
         - InstanceName
         - Region
         - InstanceType
      properties:
        InstanceName:
          title: Instance Name.
          type: string
          description: Name of the Instnace to be created.
          ui:autofocus: true
          ui:options:
            rows: 5

        Region:
          title:  AWS Region
          type: string
          description: Name of the region where you want to create your create-ec2-instance eg:- us-east-1, ap-south-1 etc.

        InstanceType:
          title: Type of Instance.
          type: string
          description: Type of the instance that you want to deploy, for eg:- t2.medium, t3.medium etc.
          enum:
            - t2.medium
            - t2.small
            - t2.micro
            - t3.medium
        action:
          title: action
          type: string
          description: What action do you want to perform? Create or delete?
          enum:
            - apply
            - destroy  
          default: apply

    - title: Choose a Repository Location
      required:
        - repoUrl
      properties:
        repoUrl:
          title: Location of the repository
          type: string
          ui:field: RepoUrlPicker
          ui:options:
            allowedHosts:
              - github.com

  steps:
    - id: fetch-base
      name: Fetching Details from content folder
      action: fetch:template
      input:
        url: ./content
        values:
          name: ${{ parameters.InstanceName }}
 
    - id: rename
      name: Rename solution and project files
      action: fs:rename
      input:
        files:
          - from: ./component-info.yaml
            to: ./${{ parameters.InstanceName }}-component-info.yaml
        
    - id: publish
      name: Publishing Details
      action: publish:github
      input:
        allowedHosts: ['github.com']
        description: This repo is to create an ec2 instance ${{ parameters.InstanceName }} using backstage.
        repoUrl: ${{ parameters.repoUrl }}
        repoVisibility: public # or 'internal' or 'private'

    - id: register
      name: Registering the new Component
      action: catalog:register
      input:
        repoContentsUrl: ${{steps['publish'].output.repoContentsUrl }}
        catalogInfoPath: '/${{ parameters.InstanceName }}-component-info.yaml'

    - id: github-action
      name: Starting GitHub action
      action: github:actions:dispatch
      input:
        workflowId: instance.yml # ID of GitHub action WorkFlow
        repoUrl: {github_repo} # 형식: 'github.com?repo={repo_name}&owner={github_Owner}'
        branchOrTagName: 'main'
        workflowInputs:
          instanceName: ${{ parameters.InstanceName }}
          awsRegion: ${{ parameters.Region }}
          instanceType: ${{ parameters.InstanceType }}
          action: ${{ parameters.action }}        

  output:
    links:
      - title: Repository
        url: ${{ steps['publish'].output.remoteUrl }}
      - title: Open in catalog
        icon: catalog
        entityRef: ${{ steps['register'].output.entityRef }}

 

  • content/index.js
console.log('Hey!! ${{ values.name }} instance is getting created. ');

 

  • content/package.json
{
    "name": ${{ values.name }},
    "private": true,
    "dependencies": {}
}

 

'Github Owner' 정보와 'Github Repo' 정보를 적절히 기입하여 생성한다.

  • content/component-info.yaml
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: ${{ values.name }}
  annotations:
    github.com/project-slug: {Github Owner}/{Github Repo}
    backstage.io/techdocs-ref: dir:.
  title: '${{ values.name | dump }} / EC2 instance'
  description: 'An example of a Instance Creation.'
spec:
  type: service
  owner: guests
  lifecycle: experimental

 

Backstage의 app-config.yaml 파일을 다음과 같이 추가한다.

  • app-config.yaml
...
catalog:  
...
  locations:
    - type: url
      target: https://github.com/{Owner}/{repo_name}/tree/{branch}/template.yaml
      rules:
        - allow: [Template]

 

 

2. Github Action 플러그인 설정


먼저 Backstage에 github action 패키지를 다운로드한다.

$ yarn --cwd packages/app add @backstage/plugin-github-actions

 

다음과 같이 frontend 코드 내용을 추가 및 수정한다.

  • packages/app/src/components/catalog/EntityPage.tsx
import { EntityGithubActionsContent } from '@backstage/plugin-github-actions';
import { EntityRecentGithubActionsRunsCard } from '@backstage/plugin-github-actions';
...
const serviceEntityPage = (
  <EntityLayout.Route path="/ci-cd" title="CI/CD">
    <EntityGithubActionsContent />
  </EntityLayout.Route>
);

...

const overviewContent = (
  <Grid container spacing={3} alignItems="stretch">
    ...
    <Grid item sm={6}>
      <EntityRecentGithubActionsRunsCard limit={4} variant="gridItem" />
    </Grid>
  </Grid>
);

 

다음과 같이 backend 코드 내용을 추가한다. 해당 코드는 Backstage 템플릿의 "action: publish:github" 동작을 위해 필요하다.

  • packages/backend/src/index.ts
const backend = createBackend();
# ...
backend.add(import('@backstage/plugin-scaffolder-backend-module-github'));
# ...
backend.start();

 

3. Github Action 파일 설정


다음과 같은 Tree 구조로 Github Action 설정파일이 위치한 곳에 Terraform ‘*.tf’ 파일을 같이 위치시킨다.

├── .github/workflows  
│   ├── instance.yml  # github action 파일
├── main.tf           # Terraform 파일
├── variables.tf      # Terraform 파일

 

Github Action을 위한 파일 생성 및 설정을 한다.

  • .github/workflows/instance.yml
    • S3_name 부분에 적절한 AWS S3 이름을 입력한다.
name: Create an ec2 instance
on:
  workflow_dispatch:
    inputs:
      instanceName:
        description: 'Name of the AWS Instance'
        required: true
      awsRegion:
        description: 'AWS Region for the instance'
        required: true
      instanceType:
        type: choice      
        description: 'AWS instance type'
        required: true
        options: 
        - t2.medium
        - t2.small
        - t2.micro
        - t3.medium        
      action:
        description: 'Action to perform (apply/destroy)'
        required: true

jobs:
  apply_ec2:
    runs-on: self-hosted
    
    if: ${{ github.event.inputs.action == 'apply' }}
    steps:
    - name: Checkout code
      uses: actions/checkout@v2  
      with:
        path: ${{ github.event.inputs.instanceName }}     

    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v2
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ${{ github.event.inputs.awsRegion }}
        
    - name: make directory
      run: mkdir -p ${{ github.event.inputs.instanceName }} > /dev/null 2>&1
      
    - name: Install Terraform state file
      run: aws s3 cp s3://{S3_name}/${{ github.event.inputs.instanceName }}/ ./${{ github.event.inputs.instanceName }} --recursive
      continue-on-error: true
      
    - name: Terraform Init
      run: terraform init
      working-directory: ${{ github.event.inputs.instanceName }}
      
    - name: Terraform Format
      run: terraform fmt
      working-directory: ${{ github.event.inputs.instanceName }}
      
    - name: Terraform Validate
      run: terraform validate
      working-directory: ${{ github.event.inputs.instanceName }}
      
    - name: terraform apply
      run: terraform apply -var instanceName=${{ github.event.inputs.instanceName }} -var awsRegion=${{ github.event.inputs.awsRegion }} -var instanceType=${{ github.event.inputs.instanceType }} -auto-approve
      working-directory: ${{ github.event.inputs.instanceName }}   
      
    - name: Upload Terraform state file
      run: aws s3 cp ./${{ github.event.inputs.instanceName }} s3://{S3_name}/${{ github.event.inputs.instanceName }} --recursive --exclude ".git*" --exclude ".terraform*"

  destroy_instance:
    runs-on: self-hosted
    
    if: ${{ github.event.inputs.action == 'destroy' }}
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v2 
      with:
        path: ${{ github.event.inputs.instanceName }}        
      
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v2
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ${{ github.event.inputs.awsRegion }}

    - name: Install Terraform state file
      run: aws s3 cp s3://wlsdn-s3/${{ github.event.inputs.instanceName }}/ ./${{ github.event.inputs.instanceName }} --recursive
      
    - name: Terraform Init
      run: terraform init
      working-directory: ${{ github.event.inputs.instanceName }}
      
    - name: Terraform FMT
      run: terraform fmt
      working-directory: ${{ github.event.inputs.instanceName }}      
      
    - name: Terraform Destroy
      run: terraform destroy -var instanceName=${{ github.event.inputs.instanceName }} -var awsRegion=${{ github.event.inputs.awsRegion }} -var instanceType=${{ github.event.inputs.instanceType }} -auto-approve
      working-directory: ${{ github.event.inputs.instanceName }}

    - name: Upload Terraform state file
      run: aws s3 cp ./${{ github.event.inputs.instanceName }} s3://{S3_name}/${{ github.event.inputs.instanceName }} --recursive --exclude ".git*" --exclude ".terraform*"

 

Github Action에서 시크릿을 사용할 수 있게 repository의 Secret을 설정한다.

  • Github repo 접속 → Settings → Secrets and variables → Actions → New repository secret

 

Github Action을 실행할 Runner를 환경에 맞게 등록 후 실행한다. 여기서는 self-hosted로 실행한다.

  • Github repo 접속 → Settings → Actions → Runners → Net self-hosted runner

 

 

4. Backstage에서 AWS 리소스 배포


Backstage를 실행 후 웹브라우저에서 UI에 접근한다.

$ yarn dev

 

 

앞전에 생성된 Template 파일을 선택한다.

 

각 입력란에 적절히 입력 후 NEXT를 클릭한다.

 

Github의 Owner와 Repository를 적절히 입력 후 REVIEW를 클릭한다.

 

실제 어떻게 생성될 것인지 입력값에 대한 정보가 출력된다. 확인 후 CREATE 버튼을 클릭한다.

 

경고 패널

주의

다음과 같은 에러가 발생하면 "NODE_OPTIONS=--no-node-snapshot" 환경 변수 설정 후 backstage를 실행해야 한다.

"When using Node.js version 20 or newer, the scaffolder backend plugin requires that it be started with the --no-node-snapshot option. Please make sure that you have NODE_OPTIONS=--no-node-snapshot in your environment."

 

Software Template에 정의되어 있는 step들이 단계별로 실행되는 걸 확인할 수 있다.

 

'REPOSITORY'를 클릭하여 생성된 Github repository와 catalog component 파일이 등록되었는지 확인한다.

 

'OPEN IN CATALOG'를 클릭하면 등록된 catalog를 확인할 수 있고, 이후 CI/CD 탭을 클릭하면 다음과 같이 미리 정의한 Github Action이 실행되는 걸 확인할 수 있다.

Message 부분을 클릭하여 좀 더 자세히 확인한다.

 

Github Action의 Workflow를 자세히 확인할 수 있고, 'JOB LOG'를 통해 실제 Terraform apply가 수행된 결과를 확인할 수 있다.

 

 

실제 EC2 리소스가 정상 생성되었는지, S3 버킷에 Terraform 파일들이 업로드되었는지, Github Action이 정상 실행 되었는지 확인한다.

 

반응형

'Developer Portal > Backstage' 카테고리의 다른 글

Backstage란? 개념부터 설치까지  (0) 2024.07.11
Backstage와 ArgoCD 통합하기  (0) 2024.06.27
Backstage와 Kubernetes 통합하기  (0) 2024.06.26

댓글