배포 자동화

배포 자동화란 한 번의 클릭 혹은 명령어 입력을 통해 전체 배포 과정을 자동으로 진행하는 것을 말합니다.

 

배포자동화 장점

  • 먼저 수동적이고 반복적인 배포 과정을 자동화함으로써 시간이 절약됩니다.
  • 휴먼 에러(Human Error)를 방지할 수 있습니다. 여기서 휴먼 에러란 사람이 수동적으로 배포 과정을 진행하는 중에 생기는 실수를 뜻합니다. 배포 자동화를 통해 전체 배포 과정을 매번 일관되게 진행하는 구조를 설계하여 휴먼 에러 발생 가능성을 낮출 수 있습니다.

 

배포 자동화 파이프라인

파이프라인 구성 단계와 수행 작업

배포에서 파이프라인(Pipeline)이란 용어는 소스 코드의 관리부터 실제 서비스로의 배포 과정을 연결하는 구조를 뜻합니다.

파이프라인은 전체 배포 과정을 여러 단계(Stages)로 분리하는데, 각 단계는 파이프라인 안에서 순차적으로 실행되며, 단계마다 주어진 작업(Actions)을 수행합니다.

파이프라인을 여러 단계로 분리할 때, 대표적으로 쓰이는 세 가지 단계가 존재합니다. 

  1. Source 단계: Source 단계에서는 원격 저장소에 관리되고 있는 소스 코드에 변경 사항이 일어날 경우, 이를 감지하고 다음 단계로 전달하는 작업을 수행합니다.
  2. Build 단계: Build 단계에서는 Source 단계에서 전달받은 코드를 컴파일, 빌드, 테스트하여 가공합니다. 또한 Build 단계를 거쳐 생성된 결과물을 다음 단계로 전달하는 작업을 수행합니다.
  3. Deploy 단계: Deploy 단계에서는 Build 단계로부터 전달받은 결과물을 실제 서비스에 반영하는 작업을 수행합니다.

여기서 주의해야 할 점은 파이프라인의 단계는 상황과 필요에 따라 더 세분화되거나 간소화될 수 있다는 겁니다.

 

AWS 개발자 도구

AWS에는 개발자 도구 섹션이 존재합니다. 개발자 도구 섹션에서 제공하는 서비스를 활용하여 배포 자동화 파이프라인을 구축할 수 있습니다.

AWS 개발자 도구 서비스 목록

 

본격적인 배포 자동화 실습을 진행하기 전에, 배포 자동화 과에서 사용하는 AWS 서비스들에 대해서 간략하게 소개하는 시간을 가져보도록 하겠습니다.

 

CodeCommit

Source 단계를 구성할 때 CodeCommit 서비스를 이용합니다. CodeCommit은 GitHub과 유사한 서비스를 제공하는 버전 관리 도구입니다. CodeCommit과 GitHub의 차이점은 무엇일까요? 어떤 서비스가 우월하다기보다, 각 서비스가 가지는 장단점이 다릅니다.

GitHub과 비교했을 때 CodeCommit 서비스는 보안과 관련된 기능에 강점을 가집니다. 소스 코드의 유출이 크게 작용하는 기업에서는 매우 중요한 요소입니다. 다만 CodeCommit을 사용할 때는 과금 가능성을 고려해야 합니다. 프리티어 한계 이상으로 사용할 시 사용 요금이 부과될 수도 있습니다. 그런 이유로 사이드 프로젝트나 가볍게 작성한 소스 코드를 저장해야 할 경우에는 GitHub을 이용하는 것이 효과적이라고 볼 수 있습니다.

CodeBuild

Build 단계에서는 CodeBuild 서비스를 이용합니다. CodeBuild 서비스를 통해 유닛 테스트, 컴파일, 빌드와 같은 빌드 단계에서 필수적으로 실행되어야 할 작업을 명령어를 통해 실행할 수 있습니다.

CodeDeploy

Deploy 단계를 구성할 때는 기본적으로 다양한 서비스를 이용할 수 있습니다. 이번 실습에서는 CodeDeploy와 S3 서비스를 이용할 예정입니다. CodeDeploy 서비스를 이용하면 실행되고 있는 서버 애플리케이션에 실시간으로 변경 사항을 전달할 수 있습니다. 또한 S3 서비스를 통해 S3 버킷을 통해 업로드된 정적 웹 사이트에 변경 사항을 실시간으로 전달하고 반영할 수 있습니다.

CodePipeline

각 단계를 연결하는 파이프라인을 구축할 때 CodePipeline 서비스를 이용합니다.

Bare minimum requirements

1. 유닛 테스트를 통과시키세요.

먼저 테스트 주도 개발을 연습합니다. 직접 test/app.test.js를 수정하여 통과하지 않는 테스트를 모두 통과시키세요.

  1. 애플리케이션은 node.js로 작성되어 있습니다. node.js LTS 버전을 준비합니다.
  2. 먼저 애플리케이션의 의존성(dependency)을 설치해야 합니다. npm install 명령을 이용해 의존성을 설치합니다.
  3. 테스트가 통과하는지 확인하려면 npm test 명령을 이용합니다. 다음과 같이 테스트가 통과하지 않는 것을 먼저 확인하세요.
➜  npm test 

> mini-node-server@1.0.0 test
> mocha ./test

Server listening on http://localhost:4000


  유닛 테스트 101
    1) 결과에 대한 기대값(expectation value)를 비교하여 유닛 테스트를 할 수 있습니다
    2) 서버에 GET / 요청을 보내면 Hello World!라는 텍스트가 응답으로 옵니다
    3) 서버에 POST /upper 요청에 body를 실어 보내면 응답은 대문자로 돌려줍니다
    4) 서버에 POST /lower 요청에 body를 실어 보내면 응답은 소문자로 돌려줍니다


  0 passing (35ms)
  4 failing

  1) 유닛 테스트 101
       결과에 대한 기대값(expectation value)를 비교하여 유닛 테스트를 할 수 있습니다:
     AssertionError: expected 2 to equal '기댓값이 채워지지 않았습니다'
      at Context.<anonymous> (test/app.test.js:13:25)
      at processImmediate (node:internal/timers:463:21)

 (생략)

test/app.test.js 파일을 열어 통과하지 않는 테스트를 수정하세요. FILL_ME_IN이라고 적힌 곳에 기댓값을 적어주면 됩니다.

2. GitHub Action을 이용해서 Node.js CI를 적용하세요.

  1. node 버전은 16 버전으로 반드시 지정해야 합니다.
  2. 다음 상황에서 GitHub Action이 트리거되어야 합니다.
    • master로 push 했을 경우
    • pull request를 보낸 경우
  3. Pull Request로 제출하세요.

Getting Started

  • node.js 프로그램의 테스트를 위해서는 npm test 명령어를 CI, 즉 GitHub Action 상에서 자동으로 실행해줘야 합니다.
  • 먼저 공식 문서를 통해 GitHub Action의 사용방법을 알아봅시다.

Creating a starter workflow

 

Creating starter workflows for your organization - GitHub Docs

Overview Starter workflows allow everyone in your organization who has permission to create workflows to do so more quickly and easily. When you create a new workflow, you can choose a starter workflow and some or all of the work of writing the workflow wi

docs.github.com

Using starter workflow

 

Using starter workflows - GitHub Docs

About starter workflows GitHub offers starter workflows for a variety of languages and tooling. When you set up workflows in your repository, GitHub analyzes the code in your repository and recommends workflows based on the language and framework in your r

docs.github.com

 


실습과정

1. 유닛 테스트를 통과시키세요.

먼저 받아온 실습파일의 의존성을 설치하고 테스트 코드를 확인하고 테스트를 실행해 어떤 테스트가 통과돼야하는지 확인한다.

app.test.js파일 내용과 npm test 실행 결과 ▼

더보기

 

const { expect } = require('chai')
const request = require('supertest')
const { app, server } = require('../app')
const FILL_ME_IN = '기댓값이 채워지지 않았습니다'

describe('유닛 테스트 101', () => {

  after(() => {
    server.close()
  })

  it('결과에 대한 기대값(expectation value)를 비교하여 유닛 테스트를 할 수 있습니다', () => {
    expect(1 + 1).to.be.equal()
    expect(100 + 200).to.be.equal()
  })

  it('서버에 GET / 요청을 보내면 Hello World!라는 텍스트가 응답으로 옵니다', () => {
    return request(app)
      .get('/')
      .then(res => {
        expect(res.text).to.be.equal()
      })
  })

  it('서버에 POST /upper 요청에 body를 실어 보내면 응답은 대문자로 돌려줍니다', () => {
    return request(app)
      .post('/upper')
      .send('"coDeStaTes"')
      .set('Content-Type', 'application/json')
      .then(res => {
        expect(res.body).to.be.equal()
      })
  })

  it('서버에 POST /lower 요청에 body를 실어 보내면 응답은 소문자로 돌려줍니다', () => {
    return request(app)
      .post('/lower')
      .send('"coDeStaTes"')
      .set('Content-Type', 'application/json')
      .then(res => {
        expect(res.body).to.be.equal()
      })
  })
})

이제 테스트 결과가 어떻게 나와야하는지 확인했으니 테스트 코드를 적절하게 바꿔준다.

변경한 app.test.js파일 내용과 npm test 실행 결과 ▼

더보기
const { expect } = require('chai')
const request = require('supertest')
const { app, server } = require('../app')
const FILL_ME_IN = '기댓값이 채워지지 않았습니다'

describe('유닛 테스트 101', () => {

  after(() => {
    server.close()
  })

  it('결과에 대한 기대값(expectation value)를 비교하여 유닛 테스트를 할 수 있습니다', () => {
    expect(1 + 1).to.be.equal(2)
    expect(100 + 200).to.be.equal(300)
  })

  it('서버에 GET / 요청을 보내면 Hello World!라는 텍스트가 응답으로 옵니다', () => {
    return request(app)
      .get('/')
      .then(res => {
        expect(res.text).to.be.equal("Hello World!")
      })
  })

  it('서버에 POST /upper 요청에 body를 실어 보내면 응답은 대문자로 돌려줍니다', () => {
    return request(app)
      .post('/upper')
      .send('"coDeStaTes"')
      .set('Content-Type', 'application/json')
      .then(res => {
        expect(res.body).to.be.equal("CODESTATES")
      })
  })

  it('서버에 POST /lower 요청에 body를 실어 보내면 응답은 소문자로 돌려줍니다', () => {
    return request(app)
      .post('/lower')
      .send('"coDeStaTes"')
      .set('Content-Type', 'application/json')
      .then(res => {
        expect(res.body).to.be.equal("codestates")
      })
  })
})

잘 실행되는 것을 확인할 수 있다.


2. GitHub Action을 이용해서 Node.js CI를 적용하세요.

이 영상을 참고해 실습을 진행했다.

테스트를 진행할 레포지토리에 .github/workflows/nodejs.yaml 파일을 생성한다.

해당 파일의 구조는 다음과 같다.

name: Node.js CI #워크플로우의 이름을 적어준다.

on: # 워크플로우가 실행되는 EVENT에 대해 작성한다. master 브랜치에 push 또는 pull request가 올 경우 실행된다.
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs: # 실행되는 워크플로우의 작업을 지정한다. 여러 Job이 있을 경우, Default로 병렬 실행을 한다.
  build: # build하는 job이 존재하고 그 아래에 실행되는 step이 존재하는 구조다.

    runs-on: ubuntu-latest # 어떤 OS에서 실행될지 지정한다.

    strategy: # 어떤 언어의 어떤 버전에서 테스트할지 지정할 수 있다.
      matrix:
        node-version: [16.x]

    steps: # 어떤 액션들을 사용할지 지정한다. uses는 이미 만들어진 액션을 사용할 때 지정한다.
      - uses: actions/checkout@v3
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm install
      - run: npm run build --if-present
      - run: npm test

 

이렇게 파일을 작성하고 GitHub Actions 탭에 들어가 작동된건지 확인해보면

잘 작동하는 것을 확인할 수 있다!!

CI/CD 파이프라인

CI/CD 파이프라인이 무엇인지는 아래 게시글에서 확인할 수 있다.

https://dratini.tistory.com/entry/DevOps-CICD-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8

[[DevOps] CI/CD 파이프라인

CI/CD 파이프라인은 소프트웨어 개발 프로세스에서 개발, 테스트 및 배포를 자동화하기 위한 과정을 말한다. 이 파이프라인은 개발자들이 소프트웨어 개발에 대한 변경 사항을 더욱 쉽고 빠르게

dratini.tistory.com](https://dratini.tistory.com/entry/DevOps-CICD-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8)

이번엔 실제로 사용되는 CI/CD를 위한 툴들을 소개한다.

CI Tools


Jenkins

Wikipedia에 따르면 Jenkins는 다음과 같이 설명할 수 있습니다.

Jenkins는 오픈소스 자동화 서버입니다. 빌드, 테스트, 배포와 같은 소프트웨어 개발의 일부분을 자동화하는 데 도움을 주며, 지속적 통합과 지속적 배포를 돕습니다. 서버 기반의 시스템이며... (중략) ... Git과 같은 버전 관리 시스템을 지원합니다. 또한 쉘 스크립트를 실행할 수 있습니다.

https://www.jenkins.io/

 

Jenkins

Meet with Jenkins Community at cdCon + GitOpsCon 2023 The Continuous Delivery Foundation (CDF) is happy to host its fourth flagship event, cdCon, taking place on May 8–9, 2023 in Vancouver, Canada as cdCon + GitOpsCon, co-organized with the Cloud Native

www.jenkins.io

www.jenkins.io](https://www.jenkins.io/)

특징

  • 설치형: 별도의 서버가 필요합니다.
  • 다양한 플러그인을 활용할 수 있습니다.
  • 쿠버네티스, Docker 등과 호환됩니다.
  • 다양한 운영체제에서 사용이 가능합니다.

Travis CI

Wikipedia에 따르면 Travis CI는 다음과 같이 설명할 수 있습니다.

Travis CI는 호스트형(hosted) 배포 자동화 서비스로, GitHub 및 Bitbucket 등에서 호스팅되는 소프트웨어 프로젝트를 빌드하고 테스트하는 데 사용됩니다.

https://www.travis-ci.com/

 

Home – Travis-CI

Simple YAML configs

www.travis-ci.com

www.travis-ci.com](https://www.travis-ci.com/)

특징

  • 클라우드 서비스(SasS) 형태로 사용할 수 있습니다.
  • Travis 자체에서 호스팅을 해주기 때문에 관리적인 측면에서 편리합니다.
  • Clojure, Erlang, Groovy Haskell, Java, JavaScirpt, Node.js, Perl PHP, Rython, Ruby 등의 다양한 언어를 지원합니다.

GitHub Action

특징

  • GitHub 저장소를 기반으로 소프트웨어 개발 Workflow를 자동화할 수 있는 툴입니다.
  • GitHub 마켓 플레이스를 통해 여러 사람이 공유한 Workflow를 찾을 수 있으며, 자신이 직접 만들어 공유할 수도 있습니다.
  • 공개 저장소를 무료로 사용할 수 있으며, 비공개 저장소 같은 경우 무료 사용량 이후에 요금이 부과됩니다.
    • 한 달에 500MB 스토리지와 실행 시간 2,000분(minute)까지 제공됩니다.
  • GitHub Actions는 GitHub 리포지토리와 완벽하게 통합되어 있어, 별도의 서드파티 도구를 사용하지 않고도 CI/CD 파이프라인을 구축할 수 있습니다. 이로 인해, 설정 및 관리가 간편해지며 개발 프로세스가 더욱 통합됩니다.
  • Github Actions 레퍼런스: Learn GitHub Actions - GitHub Docs

https://github.com/features/actions

 

Features • GitHub Actions

Easily build, package, release, update, and deploy your project in any language—on GitHub or any external system—without having to run code yourself.

github.com

github.com](https://github.com/features/actions)


지속적 통합

소프트웨어 개발 분야의 설계 및 개발 방법론에 있어서 저명한 마틴 파울러는 지속적 통합에 대해 다음과 같이 정의합니다.

팀 구성원이 각자의 작업을 자주 통합하는 소프트웨어 개발 방식 - Martin Fowler

이 방법을 도입함으로써 얻을 수 있는 장점에 대해서는 앞서 다음과 같이 설명했습니다.

  • 버그를 일찍 발견할 수 있다. → 문제 조기 식별 가능
  • 빌드 및 테스트와 같이 사람이 해야 할 일들을 자동화할 수 있다. → 업무량 감소
  • 테스트가 완료된 코드에 대해 빠른 전달이 가능하다. → 업무 효율 증가
  • 지속적인 배포가 가능해진다. → 업무 안정성 증가
  • 개발자의 생산성을 향상할 수 있다. → 업무 효율 증가

지속적 통합 원칙

  1. 단일 소스 레파지토리를 유지해야 합니다.
    • 프로젝트에서 제품을 빌드하기 위해 함께 조정해야 하는 수많은 파일이 포함되기 때문에 추적하기 편하게 만들 기 위해서라도 단일 소스 레파지토리 구조를 유지해야합니다.
  2. 빌드를 자동화해야 합니다.
    • 사람들에게 이상한 명령을 입력하게 하거나 대화 상자를 클릭하게 하는 것은 시간 낭비입니다.
    • 빌드가 수동으로 진행된다면, 수많은 실수를 낳을 수 있습니다.
  3. 셀프 테스팅 빌드를 만들어야 합니다.
    • 빌드 프로세스에 자동화된 테스트를 포함 함으로써 버그를 더 빠르고 효율적으로 파악할 수 있습니다.
    • 지속적 통합의 일부인 테스트 주도 개발(TDD)을 통해 손상된 빌드를 즉시 확인, 수정할 수 있습니다.
  4. 매일 메인라인에 커밋을 해야 합니다.
    • 각자의 진행 상황을 추적하는 데 도움이 됩니다.
    • 짧은 주기로 커밋을 하므로, 충돌이 발생한 후 빠르게 충돌 상황을 감지할 수 있습니다.
      • 해당 시점에는 충돌이 많이 발생하지 않아 문제를 쉽게 해결할 수 있습니다.
      • 만약 통합의 빈도가 길어 충돌을 늦게 발견하게 된다면 문제를 해결하기 매우 어려울 수 있습니다.
  5. 메인라인: 시스템의 현재 상태를 의미합니다. 회사마다 어떤 브랜치를 메인라인으로 두는지는 조금씩 다르지만, 여기서는 master 브랜치를 메인라인으로 취급하도록 하겠습니다.
  6. 모든 팀원이 무슨 일이 일어나고 있는지 알아야 합니다.
    • 지속적 통합은 커뮤니케이션에 관한 것이므로 모든 사람이 시스템 상태와 시스템에 적용된 변경 사항을 쉽고 명확하게 확인할 수 있어야 합니다.

이 외에도 많은 원칙들이 있다. 해당 내용들은 아래 링크에서 확인할 수 있다.

https://www.martinfowler.com/articles/continuousIntegration.html

[Continuous Integration

Every developer integrates their work into mainline at least every day.

martinfowler.com](https://www.martinfowler.com/articles/continuousIntegration.html)


지속적 통합에서 테스트가 중요한 이유

테스트를 통해 결함과 버그를 조기에 발견할 수 있으며, 이는 개발자의 생산성을 향상할 수 있습니다.

제품의 결함과 버그를 발견하고 수정하는 것은 소프트웨어의 품질을 보증하고, 더 안정적이고 사용하기 쉽게 만듭니다.


테스트 주도 개발 (Test Driven Development, TDD)

테스트 주도 개발이란 테스트가 기능의 디자인을 주도하는 반복적인 개발 방법론을 뜻합니다.

테스트 주도 개발의 필요성을 알기 위해선 TDD 이전의 개발 방식에 대해 학습할 필요가 있습니다.


기존 개발과정

기존의 개발 프로세스는 아래와 같습니다.

  1. 요구사항 분석
  2. 요구 사항을 토대로 디자인(설계)을 진행
  3. 설계에 맞추어 기능을 개발.
  4. 구현 완료 후 수동으로 기능을 테스트
  5. 원하는 대로 동작하지 않거나 문제가 발생하면 디버깅을 통해 원인을 파악하고 수정
  6. 3 - 4의 과정을 개발이 완료될 때까지 반복

기존 개발과정의 문제점

수동 테스트와 디버깅: 기능 개발 완료 후 수동으로 테스트하고 디버깅을 통해 원인을 파악하고 수정하는 과정이 시간이 많이 소요되었습니다. 그래서 개발 및 배포 속도가 느려지고, 프로젝트 전반에 걸친 효율성이 저하되었습니다.

통합 문제: 개별적으로 개발된 코드들을 일정 시점에 통합하는 과정에서 발생하는 문제점들이 있었습니다. 이러한 통합 과정에서 호환성 문제나 버그가 발생하기 쉬워, 추가적인 디버깅과 수정 작업이 필요했습니다.

협업의 어려움: 팀 간의 협업이 제한적이어서, 코드 통합 과정에서 문제가 발생할 확률이 높았습니다.

높은 유지보수 비용: 코드의 변경이나 수정이 빈번하게 발생할 때마다 수동으로 테스트를 수행하고 디버깅을 해야 하므로 유지보수 비용이 높습니다. 이는 개발 비용과 시간을 증가시키는 요인이 됩니다.


테스트 주도 개발 과정

기존의 개발 프로세스를 보완하기 위해 태어난 테스트 주도 개발(TDD)의 프로세스는 아래와 같습니다.

  1. 요구사항 분석
  2. 요구 사항을 토대로 디자인(설계)을 진행
  3. 설계를 기반으로 기능 테스트 진행
    1. 실패 시 다시 설계
  4. 테스트가 성공했다면 개발 진행
  5. 3 - 4의 과정을 개발이 완료될 때까지 반복

기존 개발환경에서 개선된 점

설계 → 개발 → 테스트로 이어지던 기존의 개발 프로세스를 설계 → 테스트 → 개발의 프로세스로 변경하여 버그를 조기에 발견하고 해결할 수 있게 되었습니다.

TDD의 설계 → 테스트 → 개발의 프로세스는 변경 점에 따라 테스트를 진행해야 하는 상황에 대한 부담을 줄여 주었습니다.


테스트 주도 개발 사이클

TDD의 테스트는 큰 단위의 문제를 작은 단위로 나누어, 지속적인 피드백을 통해 목표를 개선해 나가는 방향으로 진행됩니다.


테스트 주도 개발의 장점

  1. 더욱 명확한 기능과 구조를 설계할 수 있습니다.
  2. 재사용성이 고려된, 모듈화된 코드를 작성할 수 있습니다.
  3. 설계 수정 시간과 디버깅 시간의 단축
    1. 단위 테스트 기반의 테스트 코드를 작성하기 때문에 추후 프로그램에 문제가 발생할 경우, 각각의 모듈별로 테스트를 진행하면서 문제 지점을 쉽게 찾아낼 수 있습니다.
  4. 완성도 높은 설계
    1. 코드의 기능, 정의 등 구조적인 문제에 대하여 명확하게 접근할 수 있으며 다양한 예외상황에 대해서도 고려하게 되므로 이는 완성도 높은 설계로 이어집니다.
  5. 유지 보수의 용이성
    1. 프로젝트에 어떤 기능을 추가하는 등의 유지 보수를 해야 하는 상황이라면 항상 기존 코드들에 끼칠 영향에 대해 생각해야 합니다.
    2. TDD 이전의 개발 방식에선 단순한 기능이라도 수정되거나, 추가되는 경우에는 많은 코드에 대하여 테스트를 진행해야 했으나, TDD를 진행한다면 변경 점에 따른 테스트를 진행해야 하는 상황에 대한 부담이 줄어들 수 있습니다.

테스트의 종류

TDD에 활용할 수 있는 테스트의 종류에 대해 알아봅니다. 단위 테스트와, 통합 테스트 그리고 E2E 테스트에 대해 알아봅니다.


단위 테스트

말 그대로 작은 단위의 테스트입니다.
검증이 필요한 코드에 대해 테스트 케이스를 작성하는 과정을 말합니다.
유닛 테스트의 장점으로는 즉각적인 피드백이 나오는 것을 들 수 있습니다. 다만, 하나의 메서드가 잘 작동함은 보장할 수 있지만 그들이 결합하는 시점에서도 잘 작동하는지에 대해서는 보장할 수 없기 때문에 염두에 두어야 합니다.

통합 테스트

모듈을 통합하는 과정에서 모듈 간 호환성의 문제를 찾아내기 위해 수행되는 테스트입니다.

그래서 단위 테스트에서 찾지 못하는 통합 시 발생하는 버그 등을 찾을 수 있습니다.


단위 테스트 및 통합 테스트 시 사용하는 도구

  • mocha, chai (JavaScript)
  • JUnit (Java)

E2E 테스트 (End To End Test)

전체 시스템이 제대로 동작하는지 확인하기 위한 테스트입니다.
그래서 사용자의 입장에서 사용자가 사용하는 상황을 가정하고 시뮬레이션을 진행합니다.

장점

실제 상황에서 발생할 수 있는 에러를 미리 발견할 수 있다.

단점

테스트 작성 시 들어가는 비용이 너무 많습니다.
수행 속도가 느립니다.
"실패했다"라는 결과만 있기 때문에 피드백의 질이 낮습니다.

 

E2E 테스트에 대해 더 공부하고 싶다면 아래 링크를 통해 공부하는 것도 좋다.

https://youtu.be/wycIcpuLkzU

https://stackify.com/3-best-practices-for-end-to-end-testing/

 


통합 테스트와 E2E 테스트의 차이

항목 통합 테스트 (Integration Testing) 종단간 테스트 (End-to-End Testing)
방식 앱 구성 요소들이 함께 잘 작동하는지 확인한다. 사용자 경험을 통해 제품을 테스트함.
범위 여러 구성 요소를 포함할 수 있지만 대부분 전체 스택은 아님. 테스트 범위가 넓고 전체 애플리케이션 기술 스택을 포함함.
목적 구성 요소들이 함께 작동할 때 연결 문제 발견을 위함. 앱의 사용자 경험을 확인하기 위함.
구현 비용 비교적 저렴함. 하드웨어와 소프트웨어 측면에서 구현 비용이 더 많이 듦.
테스트 수준 단위 테스트보다 상위 수준. 통합 테스트보다 상위 수준.
수행 속도 빠름. 느림.

 

표에서 통합 테스트와 종단간 테스트 각각의 목적, 범위, 이유, 구현 비용, 테스트 수준 및 수행 속도에 대한 차이를 확인할 수 있습니다.

VPC?

AWS 서비스 안에서 클라우드 내 프라이빗 공간을 제공함으로써, 클라우드를 퍼블릭과 프라이빗으로 분리할 수 있다.

 

 VPC가 없었을 땐, 클라우드 내의 리소스를 격리할 방법이 없었다. 해서 인스턴스들이 서로 연결됨과 동시에 인터넷과도 연결되어 시스템의 복잡도를 어마어마하게 높이고, 의존도 또한 늘려버렸다. 이 말은 즉, 시스템의 유지/관리에 돈과 시간이 많이 든다는 뜻이다.

 하지만 VPC는 각 인스턴스의 구획을 분리함으로써 확장성을 가질 수 있고, 클라우드 시스템 네트워크에 대한 통제를 할 수 있다.

 

VPC 설정

VPC 설정에는 크게 4단계가 있다.

  1. 먼저 프라이빗 네트워크 안에서 사용할 수 있는 프라이빗 ip주소 범위를 설정한다.
  2. 가져온 네트워크 범위를 서브넷으로 나눠 바운더리를 나눠준다.
  3. 인터넷으로 연결되어 통신할 수 있게끔 라우팅 설정하기
  4. 접속이 허용된 사람이 프라이빗 클라우드에 접속 할 수 있도록 설정하기

VPC의 라우팅 기법 CIDR(Classless inter-domain routing)

CIDR는 사이더라고 불리우며, 클래스 없는 도메인 간 라우팅 기법으로 1993년 도입되기 시작한 국제 표준의 IP주소 할당 방법이다. ()

기존에는 클래스에 따라 정해진 Network Address와 Host Address를 사용해야 했다면, CIDR은 원하는 블록만큼 Network Address를 지정해 쓸 수 있다.

예시를 들자면, 

여기서 /16은 첫 16bit를 Network Address로 사용한다는 의미로, 총 2^16인 65,536개의 IP주소를 사용할 수 있는 네트워크 블록을 표시한다.

 

AWS에서는 RFC 1918에 명시된 ip주소를 사용할 것 권장한다. 해당 ip주소 대역을 한번 살펴보면 다음과 같다.

왜냐하면 이 RFC규약을 안지키고 ip주소 대역을 사용하면 퍼블릭 ip와 프라이빗 ip가 엉켜서 네트워크 통신에 혼선이 올 가능성이 있기 때문이다.

 

서브넷(Subnet)

서브넷은 서브네트워크(Subnetwork)의 줄임말로 IP 네트워크의 논리적인 하위 부분이다. 서브넷을 통해 하나의 네트워크를 여러 개로 나눌 수 있다. VPC를 사용하면 퍼블릭 서브넷, 프라이빗 서브넷, VPN only 서브넷 등, 필요에 따라 다양한 서브넷을 생성하면 된다.

  • 퍼블릭 서브넷 : 인터넷을 통해 연결 할 수 있는 서브넷
  • 프라이빗 서브넷 : 인터넷을 연결하지 않고, 보안을 유지하는 배타적인 서브넷
  • VPN only 서브넷 : 기업 데이터 센터와 VPC를 연결하는 서브넷

서브넷은 VPC의 CIDR 블록을 이용해 정의되며, 최소 크기의 서브넷은 4bit부분만 서브넷으로 사용하는 /28 이다. 이때 주의 할 점은 서브넷은 AZ당 최소 하나를 사용할 수 있고, 여러 개의 AZ에 연결되는 서브넷은 만들 수 없다.

VPC 서브넷 권고사항

두번째 /24 서브넷에서 가용주소의 갯수가 251개인 이유는 AWS가 확보한 서브넷 중 처음 네 개의 IP주소와 마지막 IP주소는 AWS측에서 네트워크 관리를 위해 사용하기 때문이다. 그래서 예를 들어, 10.0.0.0/24 체계의 CIDR 블록이 있는 서브넷에서 10.0.0.0, 10.0.0.1, 10.0.02, 10.0.0.3, 10.0.0.255 등 5개의 IP주소는 사용자가 사용하지 않고, AWS에서 사용한다.

 

라우팅 테이블(Routing Table)

트래픽의 전송 방향을 결정하는 라우트 규칙을 만들어 정리한 것 라우팅 테이블이라고 한다. 

즉, 라우팅 테이블은 하나의 지점에서 또 다른 지점으로 가기 위한 모든 정보를 제공하기 위한 테이블이다.

 

특정 VPC의 서브넷이 라우팅 테이블에 인터넷 게이트웨이(VPC와 인터넷 간 통신을 가능하게 하는 구성요소)를 포함하고 있다면, 해당 서브넷은 인터넷 액세스 권한 및 정보를 가지게 된다.

aws 라우팅 테이블 예시

각각의 서브넷은 항상 라우팅 테이블을 가지고 있어야 하며, 하나의 라우팅 테이블 규칙을 여러 개의 서브넷에 연결하는 것도 물론 가능하다. 서브넷을 생성하고 별도의 라우팅 테이블을 생성하지 않는다고 하더라도 AWS 클라우드가 자동으로 VPC의 메인 라우팅 테이블을 연결한다.

 

VPC 네트워크 보안

Network ACLs

서브넷 단위로 적용가능한 방화벽이다.

 다만 적용방식이 stateless(비연결) 즉, 상태를 보관하지 않는다. 그래서 인바운드로 80번 포트를 열어줬다면, 80번포트의 아웃바운드가 같이 허용되는 것이 아니라 관리자가 따로 설정해줘야만한다.

기본적으로 모든 트래픽을 block하도록 설정돼있기 때문에 연결을 하고싶다면 따로 설정해줘야한다.

AWS VPC NACL

보안 그룹

인스턴스에 대한 인바운드/아웃바운드 트래픽을 제어하는 가상 방화벽이다.

 NACL과 다른 점은 보안 그룹은 stateful(연결) 즉, 상태를 보관한다. 그래서 인바운드로 80번 포트를 열어줬다면, 기본 아웃바운드 설정이 미허용이더라도 80번 포트에서 통신을 했던 상태(state)를 기억하고 있기 때문에 따로 설정해주지 않더라도 데이터의 왕래가 가능하다.

 

VPC 간 연결: VPC peering

 인증, 로깅과 같은 기능의 공통 모듈을 만들어서 서비스 구축에 적용하고자 할 때, 이 리소스들을 외부와 격리시킨 네트워크에서 사용하도록 하고싶을 때 VPC peering기능을 사용해 데이터센터 - 데이터센터 간의 직통라인처럼 VPC간의 연결을 해서 훨씬 프라이빗한 통신을 가능하게 해준다.

 

 VPC 피어링을 통해서 VPC간의 연결을 하면, 각각 VPC가 가진 보안 그룹들을 서로 참조할 수 있어 보안성을 강화할 수 있다.

 여담으로 다른 계정이 가진 VPC와도 peering을 할 수 있다.

VPC peering 절차

VPC 피어링은 그냥 설정만 한다고 만들어지는 것이 아니다. 그래서 어떻게 만들어지는지 살펴보면

  1. 한쪽에서 피어링 요청을 시작한다.
  2. 피어링 요청을 받은 VPC가 요청을 수락하고, 수락한 응답을 요청한 쪽으로 보낸다.
  3. 양쪽에서 서로에게 통신할 수 있도록 설정하는 라우팅 테이블을 만들어준다.

이렇게 편해보이는 VPC 피어링에서 제약사항이 있다.

 위 이미지에서 볼 수 있듯이 A-C, A-D간 통신은 가능하지만, C-D경로의 통신은 불가능하다. 이것은 VPC의 프라이빗한 특성을 보호할 수 있는 특성이다.

 

서비스(회사) 네트워크와 VPC의 연결

비즈니스의 중요한 정보가 오고가는 통신환경에서 인터넷을 통해 VPC - 회사 간의 통신을 한다는 것은 보안적으로 굉장히 부담스러운 일이다. 그래서 VPC에서는 회사네트워크와 프라이빗하게 연결할 수 있는 방법을 제공하고 있다.

 

VPN(Virtual Private Network)

VPN연결 개요도

 먼저 VPN을 사용해서 프라이빗한 연결을 구현하기 위해서는 먼저 연결하고자하는 회사측의 VPN 장비가 필요하다.

이것을 AWS VPC에 있는 Gateway에 연결하면된다.

 아 혹시라도 회사의 VPN장비와 AWS VPN이 호환이 안되면 어떡하지? 라는 고민은 하지 않아도 좋다.

 VPC VPN연결을 설정할 때, 회사측의 VPN 장비가 어떤장비인지 AWS에게 알려주면, AWS측에서 맞춤 연결을 해주기 때문이다.

VPN과 AWS Direct Connect


참고 : https://www.youtube.com/watch?v=R1UWYQYTPKo 

aws 코리아 vpc 구축 기본사항 안내영상

https://inpa.tistory.com/entry/AWS-%F0%9F%93%9A-VPC-%EA%B0%9C%EB%85%90-%EC%82%AC%EC%9A%A9-%EB%B3%B4%EC%95%88-%EC%84%A4%EC%A0%95-Security-Group-NACL#security_group_%EB%B3%B4%EC%95%88%EA%B7%B8%EB%A3%B9

 

[AWS] 📚 VPC 개념 & 사용 - 보안 설정 [Security Group / NACL]

VPC 방화벽 [Security Group / NACL] 이번엔 VPC의 트래픽을 통제하고 제어하는 서비스들을 살펴보자. 이 서비스들은 흔히 특정 IP를 밴 한다거나 외국에서는 접속을 못하게 한다거나 등 이러한 방화벽

inpa.tistory.com

 

오늘은 AWS에서 제공하는 서비스들에는 어떤 것들이 있는지, 그 중에서도 ec2와 스토리지, RDS 에 대해서 자세히 알아봤다.

AWS 서비스

AWS 에는 엄청나게 많은 서비스들이 있다. 그래서 어떤 종류의 서비스가 있는지 대략적으로만 알아보자.

컴퓨트(Compute)

컴퓨트 서비스에는 클라우드에서 높은 확장성과 높은 컴퓨팅 파워를 동시에 제공하기 위한 서비스들이 있다.

이 서비스들은 기본적으로 서버와 서버리스 기반 모두의 환경 설정 기능이 제공되고, 리소스 확장과 애플리케이션의 배포를 효과적으로 만들어주는 도구들이 포함된다. 제공되는 서비스는 다음과 같다.

  • Elactic Compute Cloud(EC2)
  • EC2 Auto Scaling
  • Lambda
  • EC2 Container
  • Elastic Kubernetes

네트워킹 (Networking)

네트워킹 또한 AWS의 주력 서비스 중 하나다. 기업에서 구축한 클라우드 인프라를 외부요소, 혹은 내부의 다른 요소들과 분리/연결을 할 수 있도록 도와주는 서비스다. AWS는 네트워킹에 있어서 수많은 옵션들을 제공하고, 그것으로 애플리케이션 구성요소 간의 상호작용을 최적화 시킬 수 있다. 제공되는 서비스는 다음과 같다.

  • Virtual Private Cloud
  • Route 53
  • Elastic Load Balancing
  • Direct Connect
  • App Mesh
  • Global Accelerator

보안 (Security)

클라우드 보안은 두말할 것도 없다. AWS에서 가장 신경쓰고 있는 부분 중 하나다. 고객 데이터, 그리고 서비스 인프라를 효과적으로 보호하기 위해서 보안 레이어가 제공되고, 인프라에 관련된 여러 준수규정 매뉴얼 도 제공된다. 그 준수규정들은 다음과 같다.

  • Identity and Access Management(IAM)
  • Inspector
  • Certificate Manager
  • Directory Service
  • GuardDuty
  • Shield
  • Web Application Firewall
  • Macie
  • Secrets Manager
  • KMS

스토리지 (Storage)

AWS는 엄청 다양하고 강력한 데이터 저장 서비스를 제공하고, 고객은 각자의 비즈니스에 알맞는 스토리지 서비스를 선택할 수 있다. 제공되는 서비스는 다음과 같다.

  • Identity and Access Management(IAM)
  • Simple Shared Storage(S3)
  • Amazon Glacier
  • Elastic Block Store (EBS)
  • Elastic File System (EFS)
  • Storage Gateway
  • CloudFront

데이터베이스 (Database)

AWS는 관계형(RDBMS), 비관계형(NoSQL)같은 유형에 제한 받지 않는 다양한 데이터베이스 서비스와 웨어하우징 서비스, 인메모리 캐싱 서비스를 제공한다. 제공하는 주요 서비스 목록은 다음과 같다.

  • Relational Database Service(RDS)
  • DynamoDB
  • Redshift
  • ElastiCache
  • Aurora
  • DocumentDB

개발자 도구

AWS는 개발 환경설정같은 자잘한 업무에 대한 부담을 줄이고 좀 더 신속하게 코드를 개발/배포할 수 있는 다양한 서비스를 제공한다. S/W 라이프사이클 동안 해야 할 다양한 업무를 연속적으로 처리할 수 있도록 다양한 개발환경에서 사용할 수 있는 여러 SDK 및 도구를 제공하며 이를 이용해 작성한 코드를 즉시 배포할 수 있도록 도와주는 서비스다. AWS가 제공하는 주요 개발자 도구 서비스는 아래와 같다.

  • CodeCommit
  • CodePipeline
  • CodeBuild
  • CodeDeploy

메시징(Messaging)

클라우드로부터의 notification 수신, 애플리케이션에서의 메세지 발송, 구독자에 배포, 메시지 큐 관리 등 메시징을 위한 폭 넓은 서비스를 제공한다.

  • Simple Notification Service (SNS)
  • Simple Email Service (SES)
  • Simple Queue Service (SQS)

 

 

도커(Docker)?

 도커는 컨테이너 기반의 오픈소스 가상화 플랫폼이다. 여기서 컨테이너는 격리된 공간에서 프로세스를 실행하는 기술을 말하는데, 이 기술을 사용하면 하나의 컨테이너에 애플리케이션과 그것을 실행하는 데 필요한 모든 종속성을 포함한다.

 다시말해, 컨테이너환경에서 구축한 것은 어떤 환경에서도 똑같이 작동한다는 특징이 있다.

 

컨테이너 방식을 사용하면 얻을 수 있는 이득은 매우 많지만 몇가지만 말해보자면

  1. 의존성 충돌 문제의 해결
  2. 개발과 배포 환경의 일치
  3. 훨씬 쉬운 수평확장
  4. 각 서버에 새로운 내용을 배포하는 난이도 하락

일 것이다.

 이 장점들 모두 컨테이너안에 구축된 환경이 컨테이너 밖과 완전히 격리되는 특징으로부터 생긴다.

 그렇기 때문에 각기 다른 환경에서도 같은 컨테이너를 사용해 배포한다면, 똑같이 작동할 수 있다.

 

즉, 아래와 같은 끔찍한 상황을 미연에 방지할 수 있다.

도커의 주요 키워드

 

이미지 (Image)

컨테이너를 생성하기 위한 템플릿으로, 애플리케이션과 종속성을 포함한다. 이미지는 Dockerfile을 통해 만들어지며, 다른 사용자와 공유할 수 있다. 즉, 내 컨테이너 내부환경을 다른 사용자와 나눌 수 있다.

 

레지스트리 (Registry)

컨테이너를 생성하기 위한 템플릿으로, 애플리케이션과 종속성을 포함한다. 이미지는 Dockerfile을 통해 만들어지며, 다른 사용자와 공유할 수 있다. 즉, 내 컨테이너 내부환경을 다른 사용자와 나눌 수 있다.

 

Dockerfile 

도커 이미지를 생성하기 위한 스크립트 파일로, 애플리케이션과 실행 환경을 설정한다. Dockerfile에는 베이스 이미지, 필요한 소프트웨어 설치, 애플리케이션 코드 복사 등의 명령들이 들어간다.

 

Docker Hub

도커 이미지를 공유하는 공식 저장소로, 다양한 오픈소스 프로젝트 및 기업용 이미지를 찾을 수 있다. 개인이나 팀이 공유하고 싶은 이미지를 업로드하고 다운로드할 수 있는 플랫폼이다.

https://hub.docker.com/

 

Docker Hub Container Image Library | App Containerization

Deliver your business through Docker Hub Package and publish apps and plugins as containers in Docker Hub for easy download and deployment by millions of Docker users worldwide.

hub.docker.com

 

Docker Engine

도커 이미지를 실행하고 관리하는 소프트웨어다. 도커 엔진은 Dockerfile을 사용하여 이미지를 빌드하고, 이미지를 컨테이너로 실행한다.

 

Docker Compose

여러 컨테이너로 구성된 애플리케이션을 정의하고 관리하는 툴이다.

YAML 파일 형식으로 작성된 docker-compose.yml을 사용하여 서비스, 네트워크, 볼륨 등의 설정을 작성해두면, 한 번의 명령어로 편리하게 애플리케이션을 시작하거나 중지할 수 있다.

 

볼륨(Volume)

컨테이너의 데이터를 영구적으로 저장하는 방식이다.

컨테이너는 일시적이고 변경 가능하다. 여기서 볼륨을 사용하면 컨테이너가 삭제되거나 재시작될 때 데이터가 손실되지 않도록 데이터를 호스트 시스템에 저장할 수 있다.

물론 볼륨은 여러 컨테이너에서 공유하는 것도 가능하다.

 

도커 설치

우분투에서 도커 엔진을 설치하려면 아래 명령어를 입력해 도커 패키지를 설치해준다.

sudo apt update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

설치가 다 됐다면 한번 아래 명령어를 사용해서 제대로 설치 된건지 확인해본다.

sudo docker --version

sudo docker run hello-world

 

docker-compose 설치

우분투에서 도커 컴포즈를 설치하려면 아래 명령어를 입력해 설치해주면 된다.

 sudo apt-get update
 sudo apt-get install docker-compose-plugin

설치가 다 됐다면 한번 아래 명령어를 사용해서 제대로 설치 된건지 확인해본다.

 docker compose version

결과가 제대로 나오면 성공이다.

프록시

프록시 서버는 원 서버를 대리해서 클라이언트와 통신해 중계 역할을 하는 서버를 말한다.

프록시 서버가 클라이언트와 서버 중간에 있기 때문에 클라이언트는 프록시 서버를 '서버'라고 인식하고, 서버 입장에서는 '클라이언트'로 인식한다.

 

 

프록시의 종류

  • 리버스 프록시(reverse proxy) :  클라 - 서버 구조에서 서버를 대리해서, 클라이언트 <== 서버 방향의 요청을 중계한다.
  • 포워드 프록시(forward proxy) : 클라 - 서버 구조에서 클라이언트를 대리해서, 클라이언트 ==> 서버 방향의 요청을 중계한다.

 

프록시의 장점

  1. 로드 밸런싱: 프록시 서버는 여러 백엔드 서버 간의 트래픽을 분산시키는 로드 밸런싱 역할을 할 수 있다. 그래서 더욱 안정적인 서비스를 제공하는데 도움이 된다.
  2. 캐싱: 프록시 서버는 자주 사용되는 웹 콘텐츠를 캐시하여 응답 시간을 엄청나게 단축시킬 수 있다. 그래서 원 서버의 리소스를 덜 잡아먹을 수 있고, 네트워크 사용량을 절약해 결국 서비스 제공자 입장에서 서비스 유지 비용을 아낄 수 있다.
  3. 보안: 프록시 서버는 웹 애플리케이션 방화벽(WAF) 역할을 하여 원 서버를 외부 공격으로부터 보호할 수 있다. 그리고 사용자의 IP 주소를 숨기거나 SSL/TLS 암호화를 구현해 클라이언트와 서버 간의 통신 보안을 강화할 수 있다. Ddos와 같은 트래픽 부하 공격에게서도 원 서버를 보호해줄 수 있다.
  4. 압축: 프록시 서버는 웹 콘텐츠를 압축하여 클라이언트에게 전송할 수 있다. 그래서 데이터 전송 시간이 단축되고, 대역폭 사용량이 감소한다.
  5. 지역 제한 우회: 프록시 서버를 사용하면 특정 지역에서만 접근 가능한 웹 콘텐츠에 대한 접근을 우회할 수 있다. 사용자는 프록시 서버를 통해 해당 지역에서 차단된 웹사이트에 접속할 수 있다.(ㅎㅎㅎㅎ...)
  6. 성능 모니터링 및 로깅: 프록시 서버를 사용하면 웹 트래픽에 대한 로그를 수집하고, 성능 모니터링을 통해 시스템의 개선점을 찾을 수 있다.

 

로드 밸런서(Load Balancer)

규모가 큰 서비스의 경우에는 서버 한 대로 모든 서비스를 수용하는 것이 불가능하다. 또한 서버 1대로 서비스를 제공하는 것에 문제가 없다고 하더라고 서비스의 시스템을 단일 서버에 구축하면 해당 서버에 문제가 발생했을 때, 서비스를 제공하는데 제한이 될 수 있다.

 

그래서 서비스 가용성(availability)를 높이기 위해서 1개 서비스는 보통 여러대의 서버로 구성한다. 여기서 각 서버는 ip주소가 달라서 같은 서비스를 이용하더라도 다른 ip로 접속하는 경우가 생길 수 밖에 없는데, 여기서 1개의 서버가 문제가 생겼다면 해당 서버를 통해 서비스를 이용하던 사용자들에게 서비스 장애가 생길 수 있다. 이문제를 로드 밸런서를 사용해 해결할 수 있다.

 

로드 밸런서에는 동일한 서비스를 하는 다수의 서버가 등록되고 사용자로부터 서비스 요청이 오면 로드 밸런서가 받아 사용자별로 다수의 서버에 요청을 분산시켜 부하를 분산시킨다.

 

 

프록시 캐시

 프록시 캐시는 프록시 서버에서 원 서버가 제공하는 서비스 내용 중 일부를 저장해둬서 사용자가 해당 서비스 내용을 요청하면, 프록시 서버에서 바로 해당 서비스를 제공할 수 있도록 하는 저장공간을 말한다.

 즉, 프록시 서버가 작동하는 과정에서 프록시 서버 - 원 서버 사이의 통신시간을 절약해 훨씬 빠른 서비스 반응속도를 보여줄 수 있다.

 

이 프록시 캐시와 관련된 캐시 헤더는

 

  • Cache-Control: public 
    • 응답이 public 캐시에 저장될 수 있다.
  • Cache-Control: private
    • 응답이 private 캐시에 저장되어야한다.(특정 사용자만 사용할 수 있어야한다.)
  • Cache-Control: s-maxage
    • 프록시 캐시 전용 max-age
  • Age: 60 (HTTP 헤더)
    • 원 서버에서 응답 후 프록시 캐시 내에 머문 시간(초)

이고, 캐시를 무효화할 수 있는 헤더는

  • Cache-Control: no-cache 
    • 데이터는 캐시해도 되지만, 항상 원 서버에 검증하고 사용한다.
  • Cache-Control: no-store 
    • 데이터에 민감한 정보가 있으므로 저장하면 안된다.
  • Cache-Control: must-revalldate 
    • 캐시 만료 후 최초 조회 시 원 서버에 검증해야한다.
    • 원 서버 접근 실패 시 반드시 오류가 발생해야한다. 504오류(Gateway Timeout)
    • must-revalldate는 캐시 유효 시간이라면 캐시를 사용한다.
  • Pragma:no-cache
    • HTTP 1.0 하위 호환

 


 

참고 : https://simplicable.com/IT/proxy-server

 

 

HTTP?

HTTP(HyperText Transfer Protocol)는 웹 상에서 문서, 이미지, 동영상 등의 데이터를 주고받는 데 사용되는 프로토콜이다. 클라이언트(웹 브라우저)와 서버 간에 요청(request)과 응답(response) 방식으로 통신을 한다.

 

HTTP가 어떻게 작동하는지 실제로 통신을 하면서 알아보고 싶다면 postman과 같은 소프트웨어를 사용하는 것도 좋다.

https://www.postman.com/

 

Postman API Platform | Sign Up for Free

Postman is an API platform for building and using APIs. Postman simplifies each step of the API lifecycle and streamlines collaboration so you can create better APIs—faster.

www.postman.com

HTTP의 버전으로는 크게 HTTP/1.0, HTTP/1.1, HTTP/2, HTTP/3 가 있다. 이번 글에서는 이 버전들의 차이점에 대한 내용을 작성했다.

 


HTTP/1.0, HTTP/1.1

 HTTP/1.0은 HTTP의 초기 버전으로, 간단한 요청-응답 모델을 사용해 만들어졌다. 이 버전에서는 각 요청에 대해 별도의 TCP 연결을 생성하고 사용한 후 종료하는 방식을 사용했다. 

그래서 많은 연결이 생성되고 종료되는 오버헤드가 발생해 시스템의 자원을 낭비하는 문제가 있었다.

 

HTTP/1.1은 지속적인 연결(persistent connection)을 도입하여 연결 생성 및 종료에 대한 오버헤드를 줄였다. 또한, 파이프라이닝 기능을 통해 여러 요청을 동시에 처리할 수 있게 되어 시스템 자원 낭비를 훨씬 줄였다.

 

 

HTTP/2

HTTP/2는 다중화(multiplexing) 기능을 도입하여 여러 요청과 응답을 동시에 하나의 연결에서 처리할 수 있다.

 

 그래서 페이지 로드시간이 더욱 단축되었고, 헤더 압축 기능을 도입하여 데이터 전송량을 줄여 성능을 향상시켰다. HTTP/2는 이전 버전에 비해 보안성도 향상되었으며, 주로 HTTP에서 암호화를 통해 보안성을 강화한 HTTPS와 함께 사용된다.

 

 

HTTP/3

HTTP/3는 HTTP/2의 성능 향상을 이어받아, 더욱 향상된 통신 효율성과 속도를 가진다.

 HTTP/3의 가장 큰 특징은 기존의 TCP 대신 QUIC(Quick UDP Internet Connections) 프로토콜을 사용한다는 점이다. QUIC는 기존 TCP보다 더 낮은 지연 시간을 가지며, 패킷 손실 시에도 연결의 성능 저하가 덜하다는 장점이 있다.

 

 물론 암호화와 보안 기능을 기본적으로 제공하고, 이전 버전의 HTTP/2보다 강화된 보안성을 가진. 대부분의 최신 웹 브라우저와 웹 서버는 이미 HTTP/3를 지원하고 있습니다.

결론적으로, HTTP/3는 웹 통신의 더욱 향상된 성능과 보안성을 제공하며, 성능면에서도 우수하다. 그래서 최신 브라우저들은 대부분 HTTP/3을 지원한다. 





참고 : https://www.cloudflare.com/ko-kr/learning/ddos/glossary/hypertext-transfer-protocol-http/

 

https://ko.wikipedia.org/wiki/HTTP/3

 

HTTP/3 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전.

ko.wikipedia.org

 

소켓

 소켓은 네트워크에서 두 대의 디바이스 간의 데이터 송수신을 위한 통신 엔드포인트(도착점, 시작점)이라고 할 수 있다.

 즉, 소켓은 클라이언트-서버 모델에서 클라이언트와 서버 사이에 데이터를 전송할 수 있는 통신 경로를 제공해주는 일종의 톨게이트같은 역할을 한다.

 보통 인터넷 프로토콜(IP), 전송 제어 프로토콜(TCP), 사용자 데이터그램 프로토콜(UDP)을 사용하여 소켓 통신을 사용한다.

 

 한가지 예시를 들어보자.

 이메일을 전송할 때, 송신자의 컴퓨터와 수신자의 메일 서버 사이에는 소켓이 만들어져 이메일 데이터가 전달된다. 소켓을 통해 두 기기는 안전하고 효율적으로 통신을 할 수 있다.

포트

포트는 네트워크에서 특정 소프트웨어에 데이터를 전달하기 위한 통신 채널을 식별하는 번호다. 포트 번호는 0부터 65535까지의 범위를 가지며, 일반적으로 잘 알려진 포트 번호와 동적 포트 번호로 나뉜다.

예를 들어, HTTP 통신은 기본적으로 80번 포트를 사용하며, HTTPS 통신은 443번 포트를 사용한다.

 즉, ip주소가 우리나라의 도로명 주소라면, 포트번호는 아파트의 동 호수라고 볼 수 있다.


소켓과 포트, 둘의 차이점

 소켓은 데이터 통신을 위한 통신 엔드포인트로, 컴퓨터와 서버 간의 연결을 가능하게 하는 기능을 한다.

 그러나 포트는 특정 프로세스나 서비스에 데이터를 전달하기 위한 통신 채널을 식별하는 번호다.

 소켓은 통신 경로를 제공하고 관리하는 역할을 하며, 포트는 통신 과정에서 데이터가 어떤 프로세스나 서비스에 도달해야 하는지를 구분한다.

 즉, 소켓이 아파트 입구의 도로라고 한다면, 포트는 아파트의 동호수라고 할 수 있다.

 

 

 


참고 : https://www.geeksforgeeks.org/difference-between-socket-and-port/

 

Difference between Socket and Port? - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

https://www.ibm.com/docs/ko/ssw_ibm_i_73/rzab6/rzab6soxoverview.htm

 

소켓 프로그래밍

소켓은 네트워크에서 이름 및 주소를 지정할 수 있는 통신 연결점(종료점)입니다. 소켓 프로그래밍은 리모트 프로세스와 로컬 프로세스 간에 통신 링크를 설정하기 위해 소켓 API를 사용하는 방

www.ibm.com

 

1차 팀 프로젝트 소개

섹션1. 팀프로젝트 주제는 쇼핑몰이었다.

프로젝트의 목표는 요구사항을 충족하는 DB와 API를 설계하고 설계에 맞는 ERD와 API명세를 작성하는 것으로 시작해, 명세된 기능을 fastify를 사용해 웹서버를 구현해 제작한 API가 잘 작동하는지 확인하는 것이다.


필수 요구사항

  • 사용자는 모든 상품을 조회할 수 있다
  • 사용자는 특정 분류의 상품을 조회할 수 있다(상품분류, 브랜드명, 가격, 상품명)
  • 사용자의 타입이 판매자인 경우 자신의 상품을 등록할 수 있다
  • 사용자는 상품을 장바구니에 담을 수 있다
  • 사용자는 자신의 장바구니를 조회할 수 있다
  • 사용자는 자신의 장바구니에 있는 상품의 수량을 변경시킬 수 있다
  • 사용자는 상품을 자신의 장바구니에서 제외할 수 있다

추가 요구사항

  • 회원가입, 로그인, 로그아웃을 할 수 있다.
  • 관리자는 모든회원의 정보를 조회할 수 있다.
  • 사용자는 자신의 정보를 조회할 수 있다.
  • 관리자는 회원을 삭제할 수 있다.

DB 설계

쇼핑몰 프로젝트 ER-Diagram

필요한 테이블은 크게 users, products, Cart테이블에 더해서 product 컬럼 내용의 중복이 많을 수 있는 category와 brand는 따로 테이블을 만들어서 관리한다.

Brands와 Categories는 이 설명만으로 충분하기 때문에 나머지 테이블을 살펴보면

  • users 테이블은 pk인 id, 그리고 유저가 어떤타입(일반, 관리자, 판매자)인지 나타내는 type과 password, email 컬럼으로 구성된다.
  • products 테이블은 pk인 productid, 그리고 판매자(user)의 id인 sellerId, 해당 브랜드의 id인 brandid, 해당되는 카테고리의 id인 categoryid를 각 해당테이블과의 외래키로, 나머지는 상품명인 productName과 가격인 price, 마지막으로 상품 설명인 desc로 이뤄진다.
  • Cart 테이블은 pk인 id, 그리고 장바구니 주인(user)의 id인 userId, 장바구니에 담긴 상품의 id인 productid를 각 해당테이블과의 외래키로, 나머지는 담긴 상품의 양인 qty로 이뤄진다.

API 명세

Method Path Request Header Request Body Response Status Code Response Body
GET /product Content-Type: application.json - 200 OK [ { "sellerID": 1, "brandID": 1, "categoryID": 1, productName": "테스트 상품.", "price: 50000", "desc": "테스트용 상품입니다." }, { "sellerId": 2, "brandId": 2, "categoryId: 2, "productName": "테스트 상품2", "price": 500000, "desc": "테스트용 상품2입니다." } ]
GET /product/:productId Content-Type: application.json - 200 OK 404 Not Found { { "sellerID": 1, "brandID": 1, "categoryID": 1, productName": "테스트 상품.", "price: 50000", "desc": "테스트용 상품입니다." } }
POST /product Content-Type: application.json, Authorization { "brandId": 1, "categoryId": 1, "productName": "테스트 상품.", "price": 50000, "desc": "테스트용 상품입니다." } 201 OK 400 Bad Request 403 Forbidden { "sellerID": 1, "brandID": 1, "categoryID": 1, productName": "테스트 상품.", "price: 50000", "desc": "테스트용 상품입니다." }
PUT /product Content-Type: application.json, Authorization { "brandId": 2, "categoryId": 2, "productName": "테스트 상품 수정.", "price": 50000, "desc": "테스트용 상품 수정입니다." } 201 OK, 400 Bad Request 403 Forbidden { "brandId": 2, "categoryId":2, "productName": "테스트 상품 수정.", "price":50000, "desc": "테스트용 상품 수정입니다." }
DELETE /product/:productId Content-Type: application.json, Authorization { "productID": 1 } 200 OK 204 No Content 400 Bad Request { "Success", "Bad Request" }
GET /cart Content-Type: application.json - 200 OK [ { "cartId": 1, "productId": 1, "productName": "상품명", "brandName": "브랜드명", "categoryName": "분류명", "price": 1000, "desc": "설명" } ]
POST /cart Content-Type: application.json, Authorization { "productId": 1 } 201 OK "Success"
PUT /cart/{cartId} Content-Type: application.json, Authorization { "productID": 1, "Qty": 1 } 200 OK 204 No Content 400 Bad Request "Success", "Bad Request"
DELETE /cart/{cartId} Content-Type: application.json, Authorization { "productID": 1 } 200 OK 204 No Content 400 Bad Request "Success", "Bad Request"
GET /user Content-Type: application.json Authorization - 200 OK [ { "userId": 2, "email": test@test.com, "password": 1234 }, { "userId": 3, "email": test2@test.com, "password": 1234 } ]
GET /user/:userId Content-Type: application.json Authorization - 200 OK 204 No Content { "userId: 2, "email": test@test.com "password": 1234 }
POST /signUp Content-Type: application.json, Authorization { "email": test@test.com "password": 1234 } 201 Created 400 Bad Request "Created", "Bad Request"
POST /signIn Content-Type: application.json { "email": test@test.com "password": 1234 } 200 OK 401 Unauthorized "OK", "Unauthorized"
PUT /user/:userId Content-Type: application.json, Authorization { "email": test@test.com "password": 1234 } 200 OK 400 Bad Request "Success", "Bad Request"
DELETE /user/:userId Content-Type: application.json, Authorization - 200 OK 400 Bad Request "Success", "Bad Request"

 API 명세서를 작성하면서 어떻게 API를 구성해야 구조가 직관적이어서 구현할 때 보기 편할지 고민을 많이 했다.

 그 결과 결국 상품, 사용자, 장바구니 별로 API 경로를 구분해 작성하는 것이 구현에 있어서도, 테스트에 있어서도 가장 직관적일 것이라고 판단해 API url을 정의했다. 그리고 request body값과 response값의 정의는 개발작업 중 편의를 위해서 개발 도중 변경하는 노선을 택했다.


구현작업 중 마주했던 이슈들 

초반 프로젝트 방향성 정리 부족으로 인한 의사소통 문제

 

 처음 프로젝트를 시작할때 서비스를 만듦에 있어서 확실히 정하고 넘어가야 하는 부분에 대한 충분한 의사소통이 없었던 점이 아쉽다.

 프로젝트를 진행하면서 이 프로젝트의 부분간의 연관점을 어떻게 다룰 것인가에 대해서 미리 정하지 않아서 서로가 서로의 작업 끝나는 것을 기다리면서 시간을 허비하거나 부분간의(ex. 깃/깃허브는 어떻게 사용할지) 유기적인 상호작용이 제대로 되지 않는 문제가 있었다. 

 

의사소통 문제로 인한 작업량 불균형 문제

 

앞서 언급했던 의사소통 문제의 연장선으로, 팀원들 간의 논의가 이뤄지지 않은 문제들이 속속 튀어나오면서 개발에 익숙한 팀원들이 프로젝트를 진행하게 되는 상황이 발생해버렸다.  그바람에 개발에 익숙하지 않았던 팀원들이 작업에 제대로 참여하지 못하게 됐고, 결과적으로 역량강화를 위해서 했어야하는 경험들을 팀원들 모두가 충분히 하지는 못했다고 생각한다.

 

어떻게 해야 해결할 수 있을까?

 앞으로는 프로젝트를 진행할 때 시간을 조금 할애하더라도 팀원들간의 차이를 고려해 프로젝트의 어떤작업을 어떻게 진행할지 확실히 해두고 다음 단계로 넘어가야겠다고 다짐했다. 

 그리고 프로젝트 진행 중에도 작업이 버거워보이는 팀원들에게 먼저 도와주러 가는 태도를 더 적극적으로 가져야겠다는 생각이 들었다.

 


 

깃허브 오류 : git pull 명령어 사용시 에러( Need to specify how to reconcile divergent branches. )

 

이 오류는 클론 받아온 로컬 브랜치와 원격 브랜치가 충돌이 있는 상태에서 merge(병합)하려고 할 때 발생한다. 그래서 이 문제를 해결하려면 먼저 충돌을 해결한 뒤에 병합을 진행해야 하는데, 해결법은 다음과 같다.

  1. 로컬 브랜치의 변경 사항을 커밋해준다.
  2. 로컬 브랜치 - 원격 브랜치의 동기화를 위해서 pull을 수행해준다.
  3. 충돌이 발생한 파일을 찾아서 수동으로 충돌을 해결해준다. 충돌은 <<<<<<< HEAD, =======, >>>>>>> 같은 마커로 표시된다.
  4. 충돌이 해결된 파일을 다시 커밋하고, 병합을 완료하면 해결된다.

Postgre 데이터베이스 오류 : 보낸쿼리에서 컬럼명을 찾지 못하는 문제 에러 (column "productname" does not exist)

 

`update public.products set productName = COALESCE($1, productName), price = COALESCE($2,parsedPrice), "desc" =COALESCE($3,"desc") where id=$4`, 
              [productName, parsedPrice, desc, productId]

 이 쿼리문이 Postgre 데이터베이스에서 컬럼명을 찾지 못하는 오류였다.

 이 오류는 알고보니 'productName'컬럼명처럼 대소문자를 구분해서 작성한 구문이 있어서 생기는 오류였다.

 Postgre는 기본적으로 컬럼 이름을 소문자로 해석하는데, 그래서 나는 분명 컬럼명을 대소문자로 구분해 적었지만 Postgre는 컬럼명을 모두 소문자로 해석해서 일어난 오류이다.

 

 

이 오류를 해결하는 법은 매우 간단하다. 아래처럼대소문자를 구분해 적어둔 구문에다가 쌍따옴표를 씌워주면 된다.

`update public.products set "productName" = COALESCE($1, "productName"), price = COALESCE($2,price), "desc" =COALESCE($3,"desc") where id=$4`, 
            [productName, parsedPrice, desc, productId]

이렇게 쿼리문을 작성해주면 오류가 깔끔하게 해결되는 것을 볼 수 있다.

 

 


데브옵스 부트캠프에서 첫 프로젝트 작업이었다. 프젝 진행중에 생기는 상황을 보면서 미리 논의를 하는게 얼마나 중요한지 조금이나마 께닫는 시간이 된 것 같다. 복잡하게 흘러가는 상황에 따라오지 못하는 팀원들을 보며 안타까웠고, 앞으로도 진행할 다른 프로젝트들에 있어서는 이번 프로젝트와 같은 문제를 반복하지 않도록 신경 많이 써야겠다. 

 그리고 회고를 쓰면서 아쉬웠던 점은 구현작업 도중에 마주쳤던 오류, 이슈들에 대한 기록들을 소홀하게 했다는 것이다. 많은 에러와 싸우며 완성한 프로젝트임에도 이 해결과정을 남기지 못하는 점이 너무나도 아쉽다. 앞으로는 오류 마주칠 때마다 입맛다시면서 기록해야겠다 ㅎㅎㅎ

+ Recent posts