// 에드센스
반응형
반응형
반응형

https://media.fastcampus.co.kr/insight/why-blockchain-is-hard/

블록체인에 대해 전반적인 그림을 그리기 위해 조사하며 얻은 얕은 지식입니다.


 

1. 어디에 저장?

노드에 저장된다

블록체인을 설명할때 주로 블록은 데이터베이스, 체인은 체인 이라는 표현을 한다. 

블록에 정보를 저장하고 체인형태로 쭉 이은 구조이기 때문인데,

이러한 탈중앙화 구조로 인해 해킹에 안전하다고 한다.

 

그런데 문뜩 궁금해졌다. 거래내역과 같은 정보들은 블록에 저장된다고 하는데,

그럼 이 블록은 실제로 어디에 존재하는 것일까?

무엇이 됐든 물리적인 저장 공간은 있어야 기록이 계속 유지되지 않는가?

어딘가의 서버에 존재하면 그것은 중앙화된 구조기에 말이 안되지 않는가?

 

 

 

 


2. 블록체인 노드

블록들은 노드에 저장된다.

노드라는 단어를 많이 들었지만 정확히는 잘 몰랐다. 

우선 암호화폐 시장은 3종류의 사람들로 구성된다.

  1. 채굴자 (컴퓨터로 암호를 풀고 블록을 생성할 권한을 얻음)
  2. 소비자 (생성된 코인을 가지고 거래를 함)
  3. 네트워크 참여자 (거래가 안전한지 검토하고 승인하는 관리자)

이때 네트워크 참여자들이 사용하는 기계를 노드라고 한다.

노드는 새로운 블록이 생성되면 그 블록이 안전한 거래인지 검토하고 승인하는 역할이다.

데이터를 다운로드할 수 있는 모든 종류의 전자기기는 노드가 될 수 있다.

즉, 모든 스마트폰이나 데스크탑 PC가 노드가 될 수 있다. 

 

노드가 되려면 블록체인 네트워크에 접속하면 된다.

블록체인 네트워크에 접속하려면 데이터(블록)를 다운받거나 지갑을 생성하는 등의 행위를 의미한다.

이런 노드는 전 세계에 무수히 많이 있기에 탈중앙화었다고 말할 수 있고, 이를 해킹하기 위해선 전세계의 스마트폰과 데스크탑을 해킹해야 한다는 말이 된다.

 

검증의 원리와 과정은 다음 포스팅으로 알아보도록 하자.

 

 

 

 


3. 노드의 종류

노드는 풀노드라이트노드로 구분할 수 있다.

더 많은 종류의 노드가 있긴하지만 일단 대표적인 종류부터 알아보자.

 

 

1. 풀노드

풀노드는 모든 기능을 다 가지고 있는 노드이다.

이때 모든 기능이란

  • 지갑
  • 채굴
  • 데이터베이스
  • 라우팅

을 의미한다.

풀노드는 최초의 블록부터 최신의 블록까지 모든 블록을 가지고있다.

따라서 독자적으로 어떤 거래에 대한 검증을 할 수 있다.

모든 블록을 저장하고 있어야하기에 저장공간이 많이 필요하다.

 

비트코인의 경우 최초블록(제네시스블록)부터 현재의 블록까지 모든 정보를 저장하기 위해서는 200GB 이상의 용량이 필요하다고 한다. 

 

이곳에서 소프트웨어를 다운로드받으면 풀노드가 될 수 있다.

https://bitcoin.org/ko/download

 

다운로드 - Bitcoin

Bitcoin.org is a community funded project, donations are appreciated and used to improve the website. Make a donation

bitcoin.org

 

2. 라이트노드

라이트노드는 블록체인의 모든 블록을 가지고있지는 않다.

따라서 풀노드보다 요구되는 저장용량이 적다.

헤더정보만 가지고있는 일종의 요약본이다.

라이트노드도 거래는 가능하다. 다만 거래에 대한 검증은 불가능하다.

검증을 하기위해 풀노드에 검증된 거래인지 머클트리를 통해 확인하는 요청이 필요하다.

이를 SPV(Simple Payment Verification)라고 한다.

 

 

 

 

참고:

더보기

 

반응형
반응형

아침에 노트북을 켜서 보니 갑자기 안뜨던 에러가 떴다.

 

npm install --g eslint

 

를 해주고 인텔리제이를 재실행했더니 해결됐다.

 

(권한문제가 발생하면 sudo를 사용하자)

반응형
반응형

 

이번에 새로운 CICD를 구축할 일이 생겼는데 어떤 툴을 사용할지 조사하게 됐다.

조사하는겸 기록해보자

보고계십니까 엘리님...


1. CI/CD란?

 

"개발부터 배포까지 모든 단계를 자동화 하는 것"

 

 

CI

CI는 Continuous Integration의 약자로 지속적 통합이라는 의미이다.
애플리케이션의 새로운 코드들이 자동으로 빌드 및 테스트 되어 레포지토리에 통합되는 것을 의미한다.

 

CI의 포인트는

  • 개발자들은 최대한 작은 단위로 만들어서 개발해가며 빈번하게 merge해야한다.
  • 애플리케이션들의 빌드, 테스트, 병합하는 과정을 주기적으로, 자동화시켜야 한다.

 

이런 포인트를 따라가면

  • 병합시 충돌을 예방할 수 있고,
  • 코드의 결함이나 문제점을 빠르게 발견할 수 있다.

 

또 문제점을 빠르게 발견할 뿐 아니라 빠르게 해결할 수 있다.

왜? 작은 단위로 빈번하게 merge하기에 문제 발생 범위가 작기 때문이다(빈번하게 merge해도 빌드, 테스트가 자동이라 간편)

결과적으로 코드의 품질이 올라간다.

 

 

 

CD

CD는 Continuous Delivery/Deployment의 약자로 지속적 제공/배포라는 의미이다.
CI를 통해서 빌드, 테스트가 완료되어 배포될 준비가 끝난 애플리케이션을 개발자가 수동으로, 혹은 자동으로 배포를 진행하는 것.

(수동일때를 Continuous Delivery, 자동일때를 Continuous Deployment라고 한다. 최종단계의 자동화 여부로 나눈다고 하는듯?)

 

 

 

CI/CD 파이프라인

5줄 요약하자면,

 

  1. 개발자가 작은 단위로 코드를 짜고 메인 리포지토리에 merge를 하면,
  2. 자동으로 빌드되고,
  3. 자동으로 테스트되어,
  4. 릴리즈되고(배포준비완료),
  5. 배포된다.

 

 

 

 

 


2. CI/CD를 위한 도구

정말 많다.

https://ichi.pro/ko/hyeonjae-sayong-ganeunghan-choegoui-ci-cd-dogu-27gaji-194611649728144

 

현재 사용 가능한 최고의 CI/CD 도구 27가지

CI(지속적 통합) 및 CD(지속적 배포)(또는 CI/CD)는 소프트웨어 개발 및 DevOps 테스트의 필수적인 부분이 되었습니다. 개발자가 코드를 지속적으로 배포할 수 있도록 필요한 기능을 제공합니다.

ichi.pro

 

이 중에서 가장 대중적인 후보 3가지를 골라서 장단점을 조사해보았다.

 

 

Jenkins

  • 무료(단, 별도의 서버 필요)
  • 다양한 플러그인, IDE를 지원
  • 많은 사용자와 많은 문서
  • 규모가 작은 프로젝트의 경우 설정하는데 리소스 낭비가 발생할 수 있다.
  • 지라와 연동이 불편하거나 완벽하지 않을 수 있다.

무료라는 점과 많은 사용자를 가졌다는 점이 매력적이다.

특히 많은 문서와 선례들을 통해서 거의 모든 문제상황에 대처할 수 있을 것이다.

 

다만 하나같이 설정과 운영하는 측면에서 불편하다는 평이 많다.

손도 많이 가고 특히 별도의 서버를 준비해 거기에 설치하여 운영하는 방식이라 무료지만 실제론 무료가 아닌? 그런 느낌

(주로 t2.medium을 권하더라)

규모가 작은 프로젝트에서는 그렇게 추천하진 않는다.

 

 

 

Travis

  • 깃허브와 연동
  • 빌드 과정을 한 눈에 이해하기 쉽다.
  • 초기 설정이 젠킨스에 비해 간편(YML 파일을 통한 설정)
  • 별도의 서버 필요 없다. travis에서 알아서 VM으로 호스팅 해준다.
  • 기업용의 경우 다소 비싸다(월 129달러)
  • 로컬에서 CI환경과 동일한 빌드환경을 제공하지 않는다.
  • 젠킨스에 비해 플러그인이 다양하지 않다.
  • .travis.yml 파일을 수정하고 테스트하려면 git push를 반복해야한다.

 

 

Github Actions

  • 복잡한 과정없이 바로 깃허브에서 사용할 수 있다.
  • 빌드 과정을 눈으로 확인하기 쉽다.
  • 깃허브의 모든 이벤트에 대한 작업을 제공하고 다양한 언어와 프레임워크를 지원한다.
  • 젠킨스보다 빠르다.
  • public은 무료, private 저장소의 경우 매월 3000분 무료
  • 문서가 비교적 부족하다.
  • UI에서 개별 워크플로우 실행을 삭제할 수 없다.
  • 워크플로우에서 단일 작업만 다시 실행할 수 없다.

요즘 대세는 깃헙액션인것 같다.

빠르고, 서드파티가 필요없기 때문에.

다만 얘도 설정이 다소 복잡하다는 평이 있지만 젠킨스만큼은 아니고 그만큼 정교한 작업이 가능하다는 뜻이기도 하다.

(직접 써봐야 알겠지만ㅠ)

 

 

다음은 뱅크샐러드에서 Travis를 사용하다가 Github Actions로 전환 후 개선된 점이다.

 

 

 

 

참고:

더보기
반응형
반응형

또또 너무 오랜만에 글을 쓴다. 퇴근하면 왤케 피곤한걸까.

기존에 사용하던 stoplight에서 swagger로 전환하는 작업을 했다.

왜? 코드 안에서 바로 수정할 수 있기에 더 간편해서.

근데 한땀한땀 핸드메이드로 스웨거 주석을 작성하다가 스웨거 자동생성 라이브러리를 발견했고 그것을 사용해 보았다.


1. swagger란?

  • Open Api Specification(OAS)를 위한 프레임워크이다.
  • API들이 가지고 있는 스펙(spec)을 명세, 관리할 수 있는 프로젝트/문서
  • API 사용 방법을 사용자에게 알려주는 문서
  • express에서는 주석형태의 yaml형식으로 swagger 문서를 정의할 수 있다.
  • URL에 /api-docs으로(내가 지정한) 접근하면 swagger가 만들어주는 페이지에 접근할 수 있다.

 

 

 


2. swagger 자동 생성 라이브러리 swagger-autogen

https://github.com/davibaltar/swagger-autogen

 

GitHub - davibaltar/swagger-autogen: This module performs the automatic construction of the Swagger documentation. The module ca

This module performs the automatic construction of the Swagger documentation. The module can identify the endpoints and automatically capture methods such as to get, post, put, and so on. The modul...

github.com

 

일반적인 express swagger 적용은 이미 다른 블로그에도 친절하게 잘 적혀있다.

하지만 api가 하나 추가될때마다 한땀 한땀 주석을 작성(혹은 복붙)하기엔 너무 귀찮을 것 같았다.

 

그래서 찾아본 결과 router가 위치한 파일의 경로를 알려주면 자동으로 해당 router를 인식하고,

그 밑에 딸린 router들도 인식하여 api 문서를 위한 json파일을 자동 생성해주는 라이브러리를 찾았다.

 

 

npm start를 한 후의 동작을 보자면

  1. package.json의 prestart 스크립트를 통해서 swagger.js를 실행한다. 이 파일은 지정된 경로에 존재하는 파일들에서 라우터와 swagger 주석들을 읽는다.
  2. swagger.js의 실행 결과로 swagger-output.json 파일이 생성된다. 이 파일은 우리가 생성할 swagger 문서의 구조를 나타낸다.
  3. 생성된 json을 바탕으로 swagger 문서가 생성된다.

 

주의사항.

swagger-output.json은 prestart 스크립트에 의해서 생성되기에 nodemon처럼 저장후 자동으로 서버가 재실행 되는 경우 prestart 스크립트가 실행 안될 수 있다.

즉, 서버를 완전히 종료하고 다시 npm start를 해줘야 prestart가 실행되며 swagger-output.json 파일이 생성(갱신)된다.

 


3. 적용하기

0.  npm install

npm i swagger-ui-express swagger-autogen

 

 

1.  프로젝트 구조

├ src

└─ swagger

      └─ swagger.js

      └─ swagger-output.json

└─ loader

      └─ express.ts

 

swagger.js을 prestart 스크립트로 실행하여 swagger-output.json을 얻어내고,

express.ts에서 swagger-output.json을 참조하여 swagger 문서를 생성하는 함수를 호출한다.

 

 

2.  코드

swagger.js

const swaggerAutogen = require('swagger-autogen')({ openapi: '3.0.0' });

const options = {
  info: {
    title: 'This is my API Document',
    description: '이렇게 스웨거 자동생성이 됩니다.',
  },
  servers: [
    {
      url: 'http://localhost:3000',
    },
  ],
  schemes: ['http'],
  securityDefinitions: {
    bearerAuth: {
      type: 'http',
      scheme: 'bearer',
      in: 'header',
      bearerFormat: 'JWT',
    },
  },
};
const outputFile = './src/swagger/swagger-output.json';
const endpointsFiles = ['./src/loaders/express.ts'];
swaggerAutogen(outputFile, endpointsFiles, options);

securityDefinitions 부분은 JWT를 위한 설정이다.

저 속성을 지정하면 authorize라는 버튼이 생기며 JWT Access Token을 header에 등록할 수 있다.

여러 api들을 요청하고 응답을 확인하기위해 일일이 Access Token을 넣어줄 필요가 없다.

 

보다시피 outputFile의 경로를 지정해 주었고 swagger middleware를 설정할 때 이곳을 참조하면 된다.

endpointsFiles는 router가 위치한 파일이다. 나의 경우 이곳에 최상단 router가 있기에 이곳을 지정했다.

 

├ /api

└─ /user

      └─ /

      └─ /marketing

      └─ /phone

└─ /board

      └─ /

      └─ /admin

└─ /ping

 

이와 같은 router구조를 가진다고 했을 때, /api가 위치한 곳을 가리켜야 swagger가 만들어졌을때 

/api/user/phone

이렇게 전체 경로가 표시된다.

 

 

아무튼 이렇게 생성된 json파일을 가지고 swagger를 생성해준다.

import swaggerFile from '../swagger/swagger-output.json';
import swaggerUi from 'swagger-ui-express';

//Swagger
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerFile, { explorer: true }));

 

 

이제 /api-docs로 접속하면 다음과 같은 화면을 볼 수 있다.

 

 

근데 지금은 모든 api들이 무질서하게 나열돼 있어서 보기 안좋다.

tag를 걸어보자.

tag를 걸면서 request, response 양식도 정해주자.

 

 

다음과 같은 주석을 각 api 함수 내부에 넣어주자. 이게 좀 가독성을 해칠 수 있겠지만 그나마 가장 간단하고 편리한 방법이라고 생각한다...

다음 api는 user의 phone을 수정하는 put api이다.

  /*
   #swagger.tags = ['Users']
   #swagger.summary = '유저 phone 수정'
   #swagger.description = '유저 phone을 업데이트한다.'
   #swagger.security = [{
       "bearerAuth": []
   }]
   #swagger.requestBody = {
        required: true,
        content: {
            "application/json": {
                schema: {
                  "type": "object",
                    "properties": {
                      "phone": {
                        "type": "string",
                        "required": false
                      }
                    }
                },
                example: {
                    "phone": "010-1111-2222",
                },
            },
        }
    }
    #swagger.responses[200] = {
        description: "유저 phone 수정 완료",
        content: {
            "application/json": {
                schema: {
                    "data": {
                        "phone": "010-0000-0000",
                    }
                },
                example: {
                    "data": {
                        "phone": "010-1111-2222",
                    }
                },
            }
        }
    }
*/

 

 

태그를 Users로 지정한 것들은 다음과 같이 이쁘게 그룹화된다.

 

 

또 요청/응답 형식의 예시를 지정해 둘 수 있다.

이렇게 하면 다른 개발자들과 협업하기 편리할 것이다.

나의 경우는 api 함수 내부 가독성을 최대한 살리기위해 200 응답만 예시를 정해두었고 나머지는 생략했다.

 

추가적인 속성들은 위에 첨부한 swagger-autogen 공식 깃허브 문서를 참고하면 쉽게 따라할 수 있을 것이다.

 

 

 

 


4. 마치며

나는 응애개발자라 아직 주석이 가득한 코드가 익숙하지 않다.

자바의 경우 어노테이션으로 깔끔 세련되게 됐던거같은데 노드는 좀 애매한 것 같다..

처음부터 swagger 자동 생성을 찾아볼걸, 괜히 한땀 한땀 주석만 쓰고있느라 시간낭비 한 것 같다. 큿소!

반응형
반응형

무중단 배포에 대해 알아보자.


1. 무중단 배포란?

말 그대로 애플리케이션의 중단 없이 배포를 하는 것을 말한다. 

애플리케이션은 언제 중단될까?

 

 

v1 서비스가 실행 중일 때 v2 버전을 다운로드 받는다.
v1 서비스를 종료 시키는 시점부터 v2를 시작하기 전까지 애플리케이션은 중단된다.
이렇게 서비스가 중단되는 시간을 다운타임(downtime)이라고 한다.

 

이런 문제를 해결하기위해 무중단 배포라는 개념이 나왔고 크게 3가지 방법이 있다.

 

 

 

 

 


2. 무중단 배포 기법

1. Rolling 배포

롤링 배포는 사용 중인 인스턴스 내에서 새 버전을 점진적으로 교체하는 것으로 무중단 배포의 가장 기본적인 방식이다.

서비스 중인 인스턴스 하나를 로드밸런서에서 라우팅하지 않도록 한 뒤, 새 버전을 적용하여 다시 라우팅하도록 한다.

이를 반복하여 모든 인스턴스에 새 버전의 애플리케이션을 배포한다.

 

위 움짤을 보면

1. 4개의 기존 버전(v0.0.1)이 있다.

2. 그 중에 2개의 라우팅을 끊고 업데이트 버전(v0.0.2)로 업그레이드 한다.

3. 업데이트 버전이 배포가 되면 다시 라우팅을 시작한다.

4. 나머지 2개 인스턴스의 라우팅을 끊고 업데이트 버전을 배포한다.

5. 나머지 2개의 인스턴스의 업데이트가 끝나면 다시 라우팅을 연결한다.

 

장점

  • 인스턴스마다 차례로 배포를 진행하기에 상황에 따라 손쉽게 롤백이 가능하다.
  • 추가적인 인스턴스를 늘리지 않아도 된다.
  • 간편한 관리

단점

  • 새 버전을 배포할때 인스턴스의 수가 감소하기 때문에 사용중인 인스턴스에 트래픽이 몰릴 수 있다. 즉, 서비스 처리 용량을 고려해야 한다. 위 움짤에서 2번과정, 4번과정에서 4개의 인스턴스에서 감당하던 트래픽이 2개의 인스턴스로 몰리는 경우이다.
  • 배포가 진행될때 구버전과 신버전이 공존하기에 호환성 문제가 발생할 수 있다. 위 움짤에서 3번과정에서 업데이트 된 버전2개, 안된버전 2개가 공존하므로 사용자들은 균일한 서비스를 받지 못한다.

 

 

 

 

2. Blue Green 배포

블루는 구버전, 그린은 신버전을 의미한다.

운영중인 구버전과 동일하게 신버전의 인스턴스를 구성한 후 로드밸런서를 통해 모든 트래픽을 한번에 신버전 쪽으로 전환하는 방식이다.

 

장점.

  • 구버전의 인스턴스가 그대로 남아있어서 손쉬운 롤백이 가능하다.
  • 구버전의 환경을 다음 배포에 재사용할 수 있다.
  • 운영환경에 영향을 주지 않고 새 버전 테스트 가능.

 

단점.

  • 시스템 자원이 두배로 필요하다.
  • 새로운 환경에 대한 테스트가 전제되어야 한다.

 

 

 

 

3. 카나리 배포

옛날 광부들이 유독 가스에 민감한 카나리아 새를 이용해 가스 누출 위험을 감지했던 것에서 유래한 것으로 잠재적 문제 상황을 미리 발견하기 위한 방식이다.

신버전을 소수의 유저들에게만 배포를 해보고 문제가 없는것을 확인해가며 점차 많은 유저들에게 배포하는 기법이다.

블루그린 배포와 유사하지만 블루그린처럼 트래픽을 한번에 확 바꾸는 것이 아니라 단계적으로 전환하기에 부정적 영향을 최소화하고 상황에 따라 트래픽 양을 조절하며 롤백할 수 있다.

 

장점.

  • 문제 상황을 빠르게 감지
  • A/B 테스트로 활용 가능

 

단점.

  • 네트워크 트래픽 제어 부담.

 

*A/B 테스트란? 

대조군과 실험군으로 나누어서 특정한 UI나 알고리즘의 효과를 비교하는 방법론

 

 

 

 


3. 정리

1. Rolling 배포

  • 인스턴스를 늘리지 않고,
  • 하나씩 새로운 버전으로 늘리는 기법.
  • 사용중인 인스턴스에 트래픽이 몰리는 문제가 있다.
  • 버전간 호환성 문제가 생기는 순간이 있다.

 

 

 

2. Blue Green 배포

배포 전
배포 후

 

  • 구버전과 같은 환경으로 신버전을 미리 준비.
  • 로드밸런서의 라우팅을 한번에 확! 전환시킴.
  • 구버전의 환경을 재활용하거나 롤백하기 쉬움.
  • 단, 시스템 자원이 두배로 든다.

 

 

 

3. 카나리 배포

  • 소수만 사용하는 환경에 신버전을 배포하고 문제를 관찰한다.
  • 문제가 없으면 신버전으로의 트래픽을 단계적으로 늘린다.
  • 블루그린 배포와 유사. 하지만 블루그린은 한번에 전환, 이건 단계적으로 전환.
  • 문제를 빠르게 발견할 수 있다.

 

 

 

 

 

 

참고:

반응형
반응형

express 앱을 개발하며 자스/타스에 대해 하나씩 배워가고 있다.

오늘은 기본값 파라미터를 사용해보았다.


1. 내용

파라미터에 기본값 파라미터를 설정하면 값이 없거나 undefined가 전달될 경우 지정한 기본값으로 사용한다.

 

 

예시)

function multiply(a, b = 1) {
  return a * b;
}

console.log(multiply(5, 2));
// expected output: 10

console.log(multiply(5));
// expected output: 5

 

 

같은 개념으로 함수를 파라미터로 사용할 때도 기본값을 지정할 수 있다.

function callSomething(thing = something()) {
  return thing
}

let numberOfTimesCalled = 0
function something(){
  numberOfTimesCalled += 1
  return numberOfTimesCalled
}

callSomething()  // 1
callSomething()  // 2

 

 

앞의 파라미터를 뒷쪽의 파라미터의 기본값으로 사용할 수 있다.

function greet(name, greeting, message = greeting + ' ' + name) {
  return [name, greeting, message]
}

greet('David', 'Hi')                      // ["David", "Hi", "HiDavid"]
greet('David', 'Hi', 'Happy Birthday!')   // ["David", "Hi", "Happy Birthday!"]

 

 

 

 


2. 실제로 사용한 상황

express앱을 개발하며 user의 컨트롤러-서비스 부분을 개발하고 있었다.

서비스에서 sequelize ORM을 사용하고 있기에 쿼리문을 간단하게 생성하여 DB를 조작한다.

 

로직에서 service를 호출하는 컨트롤러에 따라 다르게 조회할 컬럼을 설정해야 했었다.

이때 기본값 파라미터를 설정해주는 것만으로 아주 간단하게 문제를 해결할 수 있었다.

 

export const findOneUserInfo = async (
  uid: string,
  attributes: string[] = ['name', 'uid', 'email', 'phone', 'createdAt'],
): Promise<any> => {
  try {
    const result = await User.findOne({
      attributes: attributes,
      where: {
        uid: uid,
      },
    });
    return result;
  } catch (err) {
    return err;
  }
};

코드를 보면 인자로 uid와 attributes를 받는다. 원래는 attributes가 없었는데 기본값 파라미터로 추가해주었다.

 

findOneUserInfo(uid)처럼 uid만 사용하여 호출했을때는 조회할 컬럼이 기본값 파라미터의 

['name', 'uid', 'email', 'phone', 'createdAt']로 지정이 되어 쿼리가 수행된다.

 

하지만 이 컬럼들 이외로 알고싶다면 

findOneUserInfo(uid, ['address', 'gender']) 이런식으로 인자를 넣어주면 된다.

이러면 기본값 파라미터 값에 ['address', 'gender']가 추가된 것이 아닌 ['address', 'gender']만 조회된다. 주의.

 

 

 

 

참고:

반응형
반응형

혼자 개발할때는 사용하지 않고 그냥 이런게 있구나 정도로 넘겼던 도커.

이제 막 배우기 시작하는 입장으로서 한번 개념만 정리해보자.


1. 부두 노동자

http://www.shippersjournal.com/news/article.html?no=26310

어학사전에 docker를 검색하면 부두(항만) 노동자라고 나온다. 

 

해외에서

화물 컨테이너가

배를 타고 부두에 도착하면

노동자들이

화물 컨테이너를 받아준다.

 

이를 도커로 바꿔보면

 

해외(도커 레지스트리)에서

화물 컨테이너(도커 이미지)

부두(나의 PC)에 도착하면

노동자(도커)들이

화물 컨테이너를 받아준다(도커 이미지를 도커 컨테이너로 만들어 실행시킨다)

 

이 비유가 맞다고 확신은 못하지만 내 생각엔 맞는 것 같다.(아니라면 알려주세요ㅠㅠ)

https://docs.microsoft.com/ko-kr/dotnet/architecture/microservices/container-docker-introduction/docker-containers-images-registries

 

 

 

 


2. 컨테이너 기반 가상화 플랫폼?

혼자 개발할때는 나의 PC에 필요한 소프트웨어들을 미리 설치해두고 개발한다. 내가 작성한 코드들은 미리 설치해둔 환경에 의존하며 실행된다. 여기까진 좋은데 내가 만든 것을 배포하려면 어떻게 해야할까? 사용자들도 나의 PC와 같은 환경이 미리 준비돼 있어야 내가 만든 SW가 정상적으로 동작할텐데 말이다. 이때 도커를 사용한다.

도커는 애플리케이션을 어떤 환경에서도 자유롭게 사용할 수 있게 해준다.

 

 

도커의 역할을 한마디로 요약해보면


"내가 만든 애플리케이션과

애플리케이션에 요구되는 환경을

몽땅 압축해서 배포하기"

 

and

 

"남이 만든 애플리케이션을

애플리케이션에 요구되는 환경과 함께

한번에 받아와서 실행하기"


정도이려나?

(피드백 달게 받습니다..)

 

 

VM과 비슷한 개념이긴 하나 VM은 OS위에 다른 OS를 생으로 다시 올린다. 각 VM은 하나의 서버를 여러 서버로 전환하는 물리적인 하드웨어의 추상화라고 하는데 이는 매우매우 무겁다.

 

도커는 여러 종류의 컨테이너가 동일한 시스템에서 실행되고 호스트의 OS 커널을 다른 컨테이너와 공유할 수 있기에 VM보다 부담이 적다. 즉, 실제로(HW적으로) 내 PC에서 공간을 나누진 않았지만 각각의 독립된 공간을 가진것 처럼 동작하도록 하는 기술이다.

 

 

 

 

이런 느낌적인 느낌?

 

 

 

 


3. 이미지, 컨테이너, 레지스트리

나의 애플리케이션, 혹은 누군가 만들어 놓은 애플리케이션의 실행에 필요한 모든 것들(라이브러리, 미들웨어, OS, 네트워크 설정 등)을 모아서 도커 이미지라는 것으로 만든다.

이 이미지들을 클라우드상에 모아놓은 곳을 레지스트리라고 한다.

대표적인 레지스트리로 도커허브가 있다.

내가 만든 이미지나 남이 만들어 놓은 이미지들을 올리고 내려받을 수 있다. 

 

 

이 이미지들을 내려받아서 실행시킨 것을 컨테이너라고 한다. 

이 고래가 도커엔진이고 위에 네모난 것들이 이미지를 내려받아 생성한 컨테이너다.

즉 도커 엔진 위에서 여러 컨테이너들이 실행중인 모습을 보여준다.

이미지를 실행한 상태인 컨테이너는 애플리케이션을 패키징/캡슐화하여 격리된 공간에서 프로세스를 동작시킨다.

일단은 내 로컬 PC 어딘가에서 프로세스가 실행되는 것이긴 한데 이 공간은 내가 접근할 수 없는 격리된 공간이다. 

 

 

컨테이너의 특징을 정리하자면

  • 가상머신과 비교하여 컨테이너의 생성이 쉽고 효율적이다.
  • 컨테이너 이미지를 통한 배포와 롤백이 간단하다.
  • 언어나 프레임워크에 상관없이 애플리케이션을 동일한 방식으로 관리한다.
  • 개발, 테스팅, 운영 환경은 물론 로컬 PC와 클라우드까지 동일한 환경을 구축한다.
  • 특정 클라우드 벤더에 종속적이지 않다.

 

 

 

 


4. 레이어

도커 이미지는 컨테이너를 실행하기 위한 모든 정보를 가지고 있기에 용량이 보통 수백MB다.

따라서 기존 이미지에서 살짝 무언가가 추가된 이미지를 다시 받는다고

이미지 전체를 다시 받으면 용량적으로 비효율적이다.

 

따라서 이미지는 레이어로 구성되어있다.

이미지1이 A+B+C의 레이어(구성 요소)로 이루어져있는데

C를 수정한 A+B+C*의 이미지를 받아야 한다면 C레이어만 C*로 수정된다.

위 그림에서 MySQL의 버전이 변경된 새로운 이미지를 내려 받는 경우를 생각해보면 될 것이다.

 

 

이미지를 내려받을 때 다음과 같은 로그가 나온다.

$ docker pull mysql

Using default tag: latest
latest: Pulling from library/mysql
69692152171a: Extracting [=======>                                           ]  4.129MB/27.15MB
1651b0be3df3: Download complete 
951da7386bc8: Download complete 
0f86c95aa242: Download complete 
37ba2d8bd4fe: Download complete 
6d278bb05e94: Download complete 
497efbd93a3e: Download complete 
f7fddf10c2c2: Download complete 
16415d159dfb: Downloading [>                                                  ]  2.157MB/115.8MB
0e530ffc6b73: Download complete 
b0a4a1a77178: Waiting 
cd90f92aa9ef: Waiting

 

 

69692152171a와 같은 한 줄 한 줄이 모두 레이어이다. 

이런 레이어가 한 겹 한 겹 쌓이고 하나의 이미지로 만들어진다.

이후 이 이미지를 docker run으로 실행하면 도커가 관리하는 파일 시스템 영역에 이미지를 복사한다.

복사 후 이미지 레이어 최상단에 컨테이너 레이어를 추가한다. 이로써 이미지는 컨테이너가 된다.

그리고 사용자에겐 Union File System을 사용하여 레이어가 스택 구조로 쌓인 이미지를 하나의 파일 시스템처럼 보이게 한다.

 

 

컨테이너 레이어는 변경 가능하고,

이미지 레이어는 변경 불가능하다.

 

컨테이너 레이어는 컨테이너 종료 시 소멸되고,

이미지 레이어는 삭제되지 않는다.

 

레이어를 사용하면 위에 말했 듯 애플리케이션의 일부를 변경할 경우 이미지 전체를 다시 다운받지 않고

변경된 부분의 레이어만 교체해 주면 된다.

 

 

 

https://m.blog.naver.com/rladnjsqll/221503641174

 

 

 

 


5. 정리

그림으로 도커의 전반적인 동작을 정리해보자

 

 

왼쪽에 (Code, OS, Other Dependencies)가 Dockerfile로 합쳐지고 이를 build하여 도커 이미지를 생성함을 볼 수 있다.

이렇게 생성된 이미지는 내 PC에 있는 도커엔진에서 실행되어 컨테이너 상태가 된다.

 

또 왼쪽에 EXE파일로 실행한 프로세스를 확인할 수 있는데 이건 내 PC위에서 실행되는 일반적인(도커가 아닌) 프로세스를 표현한 것 같다.

 

자 이제 이렇게 내 PC에서 실행되고 있는 두 프로세스(도커 컨테이너와 일반 프로세스)를 그림 우측처럼 다른 환경으로 옮기면 어찌될까?

 

도커를 통한 공유(배포)는 애플리케이션 실행을 위한 환경이 모두 준비된 이미지를 배포하기에 다른 환경이라도 이를 받아줄 수 있는 도커 엔진만 있다면 정상적으로 동작한다.

단, 일반적인 프로세스라고 표현한 저 애플리케이션은 내 PC와 다른 환경이기에 동작하지 않을 수도 있다.

 

 

 

 

 

참고:

더보기

https://futurecreator.github.io/2018/11/16/docker-container-basics/

 

도커 Docker 기초 확실히 다지기

이전 개발자를 위한 인프라 기초 총정리 포스트에서 컨테이너와 도커에 대해 간단히 살펴봤습니다. 이해하기 어려운 개념은 아니지만 막상 뭔가를 하려면 막막할 수 있는데요, 이번 포스트에서

futurecreator.github.io

https://dev-youngjun.tistory.com/2

 

도커란 무엇일까요?

Docker Preference Ubuntu 16.04 Docker CE Docker Hub 계정 목차 1. What Is Docker? 2. Docker 주요개념  2-1. 이미지(Image)  2-2. 컨테이너(Container) 1. What Is Docker? 컨테이너 기반의 오픈소스 가상..

dev-youngjun.tistory.com

https://kamang-it.tistory.com/entry/DockerDocker%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-%EB%8F%84%EC%BB%A4%EC%9D%98-%EA%B8%B0%EC%B4%88%EC%99%80-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%84%A4%EC%B9%98%ED%95%98%EA%B3%A0-%EC%82%AC%EC%9A%A91

 

[Docker]Docker는 무엇인가? 도커의 기초와 이미지 설치하고 사용(1)

요즘 도커가 대세다... 라고 말하기도 애매하게 도커가 대세가 된지는 좀 된거 같다. 도커는 여러가지의 장점과 여러가지의 단점을 가지고 있어서 무조건 좋다고 말하기는 애매한 것 같다. 일단

kamang-it.tistory.com

https://hoon93.tistory.com/48

 

도커 컨테이너(Container)와 이미지(Image)란?

도커(Docker)는 Immutable Infrastructure Paradigm 이라는 개념을 기반으로 하기 때문에, 서비스 환경(서비스 인프라) 부분을 이미지화(실행파일화)하여 배포한 뒤 가급적 변경하지 않고 사용한다고 이전

hoon93.tistory.com

https://khj93.tistory.com/entry/Docker-Docker-%EA%B0%9C%EB%85%90

 

[Docker] Docker의 개념 및 핵심 설명

  Docker란 Go언어로 작성된 리눅스 컨테이너 기반으로하는 오픈소스 가상화 플랫폼이다. 현재 Docker 0.9버전 부터는 직접 개발한 libcontainer 컨테이너를 사용하고 있다.  가상화를 사용하는 이유

khj93.tistory.com

http://itnovice1.blogspot.com/2019/08/blog-post_83.html

 

[운영체제] 커널이란?

[운영체제] 커널이란? 일반적인 커널의 형태 맨 위의 Applications가 응용 프로그램이다.  그 밑에 존재하는 것이 커널 커널 밑에 각종 하드웨어(CPU, Memory, Devices)들이 있는 것을 알 수 있다. ...

itnovice1.blogspot.com

https://eqfwcev123.github.io/2020/01/30/%EB%8F%84%EC%BB%A4/docker-image-layer/

 

docker 이미지 레이어(Docker Image Layer)

Docker Image Layer의 구조 Docker 의 이미지를 이용해서 docker run 을 하면 Docker 는 도커가 관리하는 파일 시스템 영역에 이미지를 복사한다. 복사후 docker는 이미지의 최상단에 컨테이너 레이어 라고 불

eqfwcev123.github.io

 

반응형
반응형

nodejs를 통해 애플리케이션을 개발하며 테스트를 위해 Jest 프레임워크를 사용했다.

그런데 테스트 코드 작성이 처음이기도 해서 그런지 "mock"이라는 개념에서 많이 헤맸다.

첫 테스트 코드 작성이기에 다른 블로그의 글 보다 질 좋은 정보는 없겠지만 내가 어떻게 mock을 활용하여 단위/통합 테스트를 진행했는지 나중의 나를 위해 기록해놓는다.


1. Jest란

페이스북에서 개발한 JS를 위한 테스트 프레임워크이다.

npm install --save-dev jest

이렇게 설치해 주고 사용하자. --save-dev의 의미를 모르겠다면 마침 엊그제 쓴 좋은 글이있다. ㅎㅎ

https://llshl.tistory.com/40?category=961696 

 

[NodeJS] npm install option

노드자스를 통해 개발하면서 무지성 install들을 해온것 같다. 그래서 정리해보는 시간. 1. npm options 기본적으로 npm install은 ./node_modules 폴더에 패키지를 다운받는 명령어이다. 나는 주로 npm 쓸때

llshl.tistory.com

 

 

설치를 마치고 package.json의 script에 

"scripts": {
	...
    "test": "jest",
    ...
  },

이렇게 한 줄 넣어주자. 이제 우린 npm test라는 명령을 통해 테스트를 할 수 있게 됐다.

 

 

 

 

2. mock이란

기본적인 테스트 방법은 다른 블로그들이 아주 자세히 설명하고 있으니 여기서는 굳이 기록하지 않을려 한다.

 

"목", "목하다", "모킹하다"라고 표현하는 mock이란 간단히 말해서 "가짜로 대체하기" 기능이라고 말할 수 있다.

왜 가짜로 대체하는가?

 

  • 테스트 하고싶은 기능이 다른 기능들과 엮여있을 경우(의존) 정확한 테스트를 하기 힘들기 때문

 

예를들어 request body에 사용자의 id와 password를 넣어서 post요청을 보내면 컨트롤러에서 정보를 추출한 후 데이터베이스에 넣어주는 단위테스트를 하고 싶다고 하자.

데이터베이스에 저장 요청을 보내면 성공이든 실패든 응답이 반환될 것이고 반환된 응답을 기준으로 테스트의 성공과 실패를 구분한다.

 

 

 

 

이때 실제 데이터베이스에 사용자의 id, password를 넣는 방식으로 테스트를 하는 것은 좋은 방법이 아니다.

실제 트랜잭션이 일어나기에 IO 시간도 테스트에 포함되고, 데이터베이스 연결 상태에 따라 테스트가 실패할 수도 있기 때문이다.

테스트가 실패했을 경우 내가 작성한 컨트롤러 코드의 문제인지, 데이터베이스의 문제인지 알아차리기도 힘들기 때문에 올바른 단위테스트라고 할 수 없다.

 

따라서 실제 데이터베이스에 데이터를 넣는 것이 아니라 넣은 셈 치자는 개념이다.

데이터베이스가 잘 작동하는지는 데이터베이스 관련 테스트에서 확인하면 되고 우리는 지금 controller에 대한 테스트를 진행하고 있으니 데이터베이스가 잘 작동한다는 전제를 깔고 가자는 뜻이다. 

 

데이터베이스 mocking을 표현하면 다음 그림과 같다.

 

 

 

기존의 데이터베이스 저장 메소드를 mock 함수로 만든다. mock 함수가 되면 그 함수는 아무런 기능이 없어진다. mock 함수를 호출하면 

undefined를 반환한다. 그냥 껍데기만 남기고 속을 싹 비워낸 그런 느낌

 

이제 이 mock함수를 호출했을때 반환 받기 원하는 값을 우리가 직접 지정해 준다.

본래 데이터 저장 함수이기에 데이터 저장 성공 후 응답 값이 다음과 같다고 가정해보자.

{
	"result": "success",
	"id": "testUser@test.com"
}

이런 결과와 저장된 id를 json으로 응답 받았었다면 mock함수가 반환하길 원하는 값으로 저 json을 지정해주자.

우리는 controller의 로직에 집중해야하니 데이터베이스는 "대충 이런이런 값을 반환한다고 치자"라고 하고 넘어가는 개념이다.

 

 

 

 

3. 작성해본 테스트 코드

모킹을 하는 방법은 Jest에서 제공하는 fn(), spyOn(), mock() 함수를 사용하면 된다. 이 중에서 난 fn()을 쓰려고 했지만 원인 모를 에러로 인해 spyOn 함수를 사용했다! 

이 3가지 함수에 대한 설명은 이 블로그에서 친절하게 설명을 해두었다.

 

 

UserController에 있는 getPassword함수를 테스트해보자.

getPassword 함수는 다음과 같다.

export const getPassword = async (req: Request, res: Response, next: NextFunction) => {
  try {
    const id: string = req.query.id ? req.query.id : req['claims']['email'];
    return res.status(200).json(await account.findPasswordById(id));
  } catch (e) {
    Logger.error('%o', e);
    return next(e);
  }
};

getPassword함수는 사용자 Id를 받아서 db에서 password를 조회하는 함수다.

이 함수의 기능은 두가지다.

  1. req로부터 사용자 id를 추출하고,
  2. findPasswordById함수에 사용자 id를 넘겨준다.

http 요청을 만들기 위해 httpMocks라는 모듈을 사용하였다.

 

 

 

작성한 테스트코드는 다음과 같다.

import * as model from '../models/student.model';

test('[DB 데이터 조회 테스트] 사용자 id를 통해 password 조회 테스트', async () => {
  const req = httpMocks.createRequest({
    method: 'GET',
    url: '/api/test?id=user@test.com',
  });
  const res = httpMocks.createResponse();
  const next = null;
  const expectedResult = { password: 'mypass123' };
  const mockFindPasswordById = jest.spyOn(model, 'findPasswordById');
  mockFindPasswordById.mockResolvedValue(expectedResult);

  await UserController.getPassword(req, res, next);

  expect(res.statusCode).toBe(200);
  expect(res._getJSONData()).toStrictEqual(expectedResult);
});

import한 model은 데이터베이스에 접근하는 함수들을 모아놓은 모듈이다.

 

 

 

11번째 줄의 spyOn함수를 통해서 mock 함수를 생성한다.

  const mockFindPasswordById = jest.spyOn(model, 'findPasswordById');

 

import된 model 모듈에 존재하는,

findPasswordById 함수를 spyOn을 통해서,

mockFindPasswordById 라는,

mock함수로 만들었다.

 

 

findPasswordById는 원래 데이터베이스에 접근하는 함수였지만 이제 아무런 기능이 없는 mock 함수가 되었다.

호출하면 undefined만 반환한다.

 

 

우리는 이 함수가 데이터베이스에서 password를 조회해 반환해주기를 원한다. 그렇기에 우리가 원하는 반환 값을 직접 달아준다.

  mockFindPasswordById.mockResolvedValue(expectedResult);

이제 mockFindPasswordById는 쿡 찌르면 우리가 지정해준 응답값을 반환해준다.

이는 "데이터베이스가 정상적으로 동작했다고 치자"라고 설정한것이다.

 

 

데이터베이스 관련 함수는 async/await으로 처리되기에 Promise를 resolve하는 mockResolvedValue를 사용했다. 

다른 여러 종류의 반환값에 대한 반환값 지정 함수는 다음 문서에서 확인하면 된다.

https://jestjs.io/docs/mock-function-api

 

Mock Functions · Jest

Mock functions are also known as "spies", because they let you spy on the behavior of a function that is called indirectly by some other code, rather than only testing the output. You can create a mock function with jest.fn(). If no implementation is given

jestjs.io

 

 

 

이제 우리는 UserController.getPassword(req, res, next)로 우리가 만들었던 req를 보내준다.

UserController.getPassword(req, res, next)에는 findPasswordById함수가 있는데 우리는 지금 이걸 mock으로 만들어 주었으니 무조건 우리가 지정한 응답을 줄 것이다.

  await UserController.getPassword(req, res, next);

  expect(res.statusCode).toBe(200);
  expect(res._getJSONData()).toStrictEqual(expectedResult);

UserController.getPassword에 잘못된 점이 없다면 테스트는 성공한다.

 

 

 

 

4. 결론

우리는 이제 무엇에 테스트를 성공한것인가?

 

 

UserController의 getPassword함수에서 

  1. req로부터 사용자 id를 추출하고,
  2. findPasswordById함수에 사용자 id를 넘겨준다.

이 두가지 로직에 대한 검증이 성공한 것이다.

(매우매우 간단한 테스트다)

 

 

이는 작은 단위의 단위테스트이고 supertest를 활용한 통합테스트도 작성하였다. 오히려 통합테스트가 더 직관적이라 테스트코드를 처음 작성해보는 나에겐 더 쉬웠다.

라고 말하지만 사실 통합테스트에서는 db의 endpoint를 test일 경우에만 local db로 동적으로 바꿔주는 작업 때문에 많이 고생했다.ㅠㅠ

다음 포스팅에서 하소연 해보겠다.

 

 

 

 

 

참고:

반응형

+ Recent posts