(326)

[NestJS] JWT secretOrPrivateKey must have a value

에러 메세지 원인 .env에 작성해 둔 JWT_SECRET이라는 이름의 환경변수를 읽는 과정에서 발생한 에러로 JWT 토큰 발행 시 반드시 secret key가 있어야 하는데 읽지를 못한 것으로 보인다. 공식 문서의 JWT 가이드 대로 따라하며 토큰 발급 시 발생했으며, 모듈에서 설정한 secret을 JwtModule에 등록하지 못했다. 환경변수를 읽어오기 전에 secret가 먼저 register되는 것 같아 보였다. @Module({ imports: [ UserModule, JwtModule.register({ global: true, secret: process.env.JWTSecret, signOptions: { expiresIn: '60s' }, }), TypeOrmModule.forFeature..

[AWS] 왜 내 S3 버킷정책은 작동하지 않는가? (로드밸런서 로그 생성 및 S3 파일 자동 삭제 정책 생성하기)

서론 현재 간단하게 개발하고 싶은 것들을 실서버에 배포하고 계속해서 무언가를 적용해보려고 EC2 인스턴스를 통해 도메인을 등록, 배포 후 ALB를 적용시킨 상태이다. 모의투자, 웹소켓을 이용한 실시간 랜덤 데이터를 직접 만들고 투자할 수 있게 방향성을 잡고있는데, 우선 배포와 노드에 대한 적응이 먼저라 생각하여 간편한 계산기 애플리케이션을 만들었고 계속 여기에 무언가 해 나갈 생각이다. 각종 간편 계산기 모음 복리 계산기, 전역일 계산기, 기본 계산기 등 간단하게 사용할 수 있는 계산기를 제공합니다. mananaweb.net NGINX 502 에러 로그를 확인하고 싶은데, 단순 NGINX에는 에러로그가 남지 않아서 ALB 문제일 것이라 생각하여 S3 버킷에 로드밸런서 로그를 남겨놓은 상태이다. 과금때문에..

[NestJS] 예외처리, Exception Filter (HttpException, Error Handling]

서론 노드로 전향한지 만 1개월이 되었다. 현재 근무하고 있는 곳의 애플리케이션 코드를 보면, 따로 예외처리를 해주는 부분이 없어 에러 핸들링과 에러 로깅 작업을 커스텀으로 진행하려고 한다. 이를 위해 공식 문서를 활용해가며 학습할 필요를 느껴서 공식문서에 해당 내용을 확인하고 학습해보자. NestJS의 예외 처리 Nest에는 애플리케이션 내에서 처리되지 않은 모든 예외를 처리하는 내장 예외 레이어가 존재한다. 애플리케이션 코드에서 예외 처리가 되지 않으면 예외 레이어에서 예외를 처리한다. 기본적으로 이 작업은 HttpException유형과 하위 클래스의 예외를 처리하는 내장 전역 예외 필터에 의해 수행된다. 예외를 인식할 수 없는 경우 다음과 같은 500에러를 내보낸다. { "statusCode": 5..

[Docker] 도커 권한 문제 해결하기 / permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock:

에러 발생 경위 도커라이징하여 EC2 인스턴스로 배포를 계속 시도해보다가, 용량문제로 아래와 같은 에러가 발생했다. failed to register layer: write /usr/src/app/node_modules/sockjs/Changelog: no space left on device 단순 인스턴스의 공간 문제인 것 같아 아래 명령어를 통해 디스크 용량을 확인해보았다. $ df -h 용량을 거의 다 차지하고 있는 것 같아서, 도커 이미지를 여러번 등록하기만 했지 삭제하지 않았던 것이 생각나서 확인해보았더니 이미지가 엄청 많았다. 하나하나 일일이 IMAGE ID로 지워주기 힘들 것 같아 태그네임이 인 모든 이미지파일을 지우는 명령어를 알려달라고 GPT에게 부탁했더니 아래 명령어를 던져주었고, 실행..

[NestJS] Docker와 Git Actions, EC2를 활용한 CI/CD 환경 구축 - 도커라이징하여 EC2에 배포하기

서론 사내 서비스가 Git Actions, Docker, EC2, PM2를 활용해 CI/CD 및 파이프라인을 구축하여 사용하고 있었기에 학습이 필요하다.. 간단하게 따라해볼 무언가를 찾아서 따라해보면서 학습할 것이 필요했는데 초행이다보니 같은 환경에서 진행한 분을 찾기 어려워 외국 유튜브 영상이나 기타 해외 자료들을 활용하며 따라하며 공부했다. CI/CD란? Continuos Integration / Continuous Delivery의 약자로 단어 뜻대로 지속적 통합과 배포를 뜻한다. DockerFile 작성 Docker에 이미지를 빌드하고 컨테이너 생성을 하는 과정은 단순하고 Nodejs docs에서도 가이드라인을 제시해준다. https://nodejs.org/ko/docs/guides/nodejs-..

[Javascript] using : 자바스크립트의 새로운 변수 키워드

서론 즐겨보는 개발 관련 유튜브 중 하나인 노마드코더에서 흥미로운 영상을 게시했다. 기존 자바스크립트의 변수 const, var, let을 대체할 강력한 키워드인 using 이라고 소개했다. 역시 프로유튜버답게(??) 어그로를 잘 끄셔서 자연스레 정주행했다. 현재 자바에서 타입스크립트로 전향한 지금, 꽤나 중요한 이슈사항이 될 것 같아서 포스팅하여 정리해보려고 한다. https://www.youtube.com/watch?v=-NmwyJ5S-IY&t=151s 기존의 자바스크립트 변수 간단하게 기존 변수들에 대해 알아보자. es6에서 포함된 기능중 하나는, 변수 선언에 사용할 수 있는 키워드인 let, const의 추가였다. 혹여 단순 using에 대한 정보만을 얻고자한다면, 스크롤을 많이 내려야 할 것 같..

[Docker] Container 실행하자마자 exit 되는 경우 / docker ps 아무것도 없을 때

에러 상황 EC2, Github Actions, Docker을 통한 CI/CD 파이프라인 구축 실습 중 발생한 상황이다. Github의 Action runner를 활용하여 CD를 수행했을 때 실행중인 컨테이너에 아무것도 없는 모습이다. 해결 1. 아래 명령어를 사용해 모든 컨테이너 목록을 조회했다. docker ps -a 분명 실행은 되었지만 종료된 모습이다. 2. 로그를 찍어 보니 해당 에러를 확인할 수 있었다. docker logs [컨테이너 NAMES] 나의 경우 Dockerfile의 마지막 줄에 실행 명령어로 node dist/main.js을 사용하였는데 해당 모듈을 찾을 수 없다는 에러였다. 해결 Dockerfile에서, 설정 경로와 카피 경로를 다시 설정하고, 실행 명령어를 바꿔주었더니 동작하..

윈도우 SSH접속 시 pem 파일 권한 변경 (윈도우 chmod 400)

사내에서 mac os를 사용해서 개발을 하다가 집에서 해당 기술들을 학습한 후 토이프로젝트 적용을 시켜보려고 ec2를 실행시킬 일이 있어서 pem파일을 chmod 400을 통해 권한을 설정하려고 했는데 권한 설정이 불가능하여 접속이 되지 않았다. 이럴 때 아래와 같은 명령어를 사용하자. 반드시 CMD에서 사용해야하며 icacls.exe 경로+pem이름 뒤 명령어를 입력하면 된다 icacls.exe myec2.pem /reset icacls.exe myec2.pem /grant:r %username%:(R) icacls.exe myec2.pem /inheritance:r

[Git Actions / Docker] An image does not exist locally with the tag: [repo]/[image]

에러 메세지 An image does not exist locally with the tag: [repo]/[image] 아래와 같은 action.yml을 사용하고있었는데 계속해서 리파지토리에 이미지를 빌드한 후 태그를 못잡아 주는 것 같았다. name: Docker Image CI on: push: branches: [ "main" ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Login Dockerhub env: DOCKER_USERNAME: ${{secrets.DOCKERHUB_USERNAME}} DOCKER_PASSWORD: ${{secrets.DOCKERHUB_PASSWORD}} run: docke..

[NestJS] JWT secretOrPrivateKey must have a value

Tech/트러블슈팅 2023. 11. 2. 11:21
728x90
728x90

 

 

에러 메세지

 

 

 

원인

.env에 작성해 둔 JWT_SECRET이라는 이름의 환경변수를 읽는 과정에서 발생한 에러로 JWT 토큰 발행 시 반드시 secret key가 있어야 하는데 읽지를 못한 것으로 보인다.

 

공식 문서의 JWT 가이드 대로 따라하며 토큰 발급 시 발생했으며, 모듈에서 설정한 secret을 JwtModule에 등록하지 못했다.

환경변수를 읽어오기 전에 secret가 먼저 register되는 것 같아 보였다.

@Module({
  imports: [
    UserModule,
    JwtModule.register({
      global: true,
      secret: process.env.JWTSecret,
      signOptions: { expiresIn: '60s' },
    }),
    TypeOrmModule.forFeature([
    ])
  ],
  providers: [AuthService],
  controllers: [AuthController],
  exports: [AuthService],
})
export class AuthModule {}

 

 

Documentation | NestJS - A progressive Node.js framework

Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Rea

docs.nestjs.com

 

 

 

 

 

해결

JwtModule의 registerAsync 함수를 사용했음.

이름에서도 유추할 수 있듯이 등록이 되는 것을 아래 프로퍼티들을 읽어올 때 까지 기다렸다 완료시키는 함수이다.

ConfigService를 주입하여 config에 있는 환경변수를 읽어오는 작업을 수행하게 할 수 있다.

@Module({
  imports: [
    UserModule,
    JwtModule.registerAsync({
      inject: [ConfigService],
      global: true,
      useFactory: (config: ConfigService) => ({
        secret: config.get<string>('JWT_SECRET'),
        signOptions: { expiresIn: config.get<string>('expiresIn') },
      })
    }),
    TypeOrmModule.forFeature([
    ])
  ],
  providers: [AuthService],
  controllers: [AuthController],
  exports: [AuthService],
})
export class AuthModule {}

 

 

 

 

참조

 

NestJs - ConfigModule.forRoot isGlobal not working

I am trying to load the "process.env.AUTH_SECRET" in AuthModule, but it is giving me the undefined error "Error: secretOrPrivateKey must have a value". I did setup the "isG...

stackoverflow.com

 

NestJS - can't resolve ConfigService

I am using NestJS jwt passport to auth user. I follow the doc, here is my app module: import { Module } from '@nestjs/common'; import { UserModule } from './user/user.module'; import { TypeOrmMod...

stackoverflow.com

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[AWS] 왜 내 S3 버킷정책은 작동하지 않는가? (로드밸런서 로그 생성 및 S3 파일 자동 삭제 정책 생성하기)

Tech/트러블슈팅 2023. 10. 20. 17:14
728x90
728x90

 

 

 

서론

현재 간단하게 개발하고 싶은 것들을 실서버에 배포하고 계속해서 무언가를 적용해보려고 EC2 인스턴스를 통해

도메인을 등록, 배포 후 ALB를 적용시킨 상태이다.

 

모의투자, 웹소켓을 이용한 실시간 랜덤 데이터를 직접 만들고 투자할 수 있게 방향성을 잡고있는데, 우선 배포와 노드에 대한 적응이 먼저라 생각하여 간편한 계산기 애플리케이션을 만들었고 계속 여기에 무언가 해 나갈 생각이다.

 

각종 간편 계산기 모음

복리 계산기, 전역일 계산기, 기본 계산기 등 간단하게 사용할 수 있는 계산기를 제공합니다.

mananaweb.net

 

NGINX 502 에러 로그를 확인하고 싶은데, 단순 NGINX에는 에러로그가 남지 않아서 ALB 문제일 것이라 생각하여 S3 버킷에 로드밸런서 로그를 남겨놓은 상태이다.

 

과금때문에 3일이 지난 것들은 삭제되게 규칙을 생성한 후 5일 뒤 접속해보았는데 하나도 삭제되지 않은 상황.

아래는 내 정책 에 대한 설정이다.

 

혹시 로그 설정이 필요하다면 아래 aws doc를 참조하면 쉽게 따라할 수 있다.

 

Application Load Balancer 액세스 로그 활성화 - Elastic Load Balancing

Application Load Balancer 액세스 로그 활성화 로드 밸런서에 대한 액세스 로그를 활성화할 때는 로드 밸런서가 로그를 저장할 S3 버킷의 이름을 지정해야 합니다. 버킷에 액세스 로그를 쓰는 Elastic Load

docs.aws.amazon.com

 

 

 

수명 주기 규칙 재생성

제대로 작동하지 않는 것 같아 밀어버리고 다시 생성했다.

 

1. 접두사에 특정 디렉토리를 적어준다. 나는 버킷 내부에 AWSLogs의 디렉토리 내부에 생성된다.

 

 

 

2. 객체의 현재 버전 만료, 이전 버전 영구 삭제를 체크했다.

1일이 지나면 만료처리가 되고, 만료된지 1일 지나면 삭제처리될 것이다.

빠른 확인을 위해 이렇게 셋팅했으며, 돌아가는 것을 확인하여 정상 동작한다면 수정할 것이다.

 

경과는 아래에 재포스팅할 수 있도록 하겠음. 2일 뒤에 보자...

 

 

 

2023 11 17 추가
정상적으로 잘 동작하는 것을 확인할 수 있었다, 3일이 지난 로그들은 자동 삭제되는중..
728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[NestJS] 예외처리, Exception Filter (HttpException, Error Handling]

Tech/NodeJS 2023. 10. 9. 16:46
728x90
728x90

 

 

 

서론

노드로 전향한지 만 1개월이 되었다.

현재 근무하고 있는 곳의 애플리케이션 코드를 보면, 따로 예외처리를 해주는 부분이 없어 에러 핸들링과 에러 로깅 작업을 커스텀으로 진행하려고 한다. 이를 위해 공식 문서를 활용해가며 학습할 필요를 느껴서 공식문서에 해당 내용을 확인하고 학습해보자.

 

 

 

NestJS의 예외 처리

Nest에는 애플리케이션 내에서 처리되지 않은 모든 예외를 처리하는 내장 예외 레이어가 존재한다. 애플리케이션 코드에서 예외 처리가 되지 않으면 예외 레이어에서 예외를 처리한다.

 

기본적으로 이 작업은 HttpException유형과 하위 클래스의 예외를 처리하는 내장 전역 예외 필터에 의해 수행된다. 예외를 인식할 수 없는 경우 다음과 같은 500에러를 내보낸다.

{
  "statusCode": 500,
  "message": "Internal server error"
}

 

 

 

HttpException

Nest는 @nestjs/common의 HttpException을 제공하며 이를 통해 다음과 같이 403에러에 대한 예외처리를 할 수 있다.

@Get()
async findAll() {
  throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}
{
  "statusCode": 403,
  "message": "Forbidden"
}

 

기본 형태의 error response JSON을 재정의할 수 있다.

단 statusCode는 유효한 상태 코드여야한다.

@Get()
async findAll() {
  try {
    await this.service.findAll()
  } catch (error) { 
    throw new HttpException({
      status: HttpStatus.FORBIDDEN,
      error: 'This is a custom message',
    }, HttpStatus.FORBIDDEN, {
      cause: error
    });
  }
}
{
  "status": 403,
  "error": "This is a custom message"
}

 

@nestjs/common의 HttpStatus Enum을 사용하면 간편하게 상태코드를 정의할 수 있다.

 

 

Custom Exception

커스텀 Exception을 생성해야 하는 경우, HttpException에서 상속되는 예외 계층을 만드는 것이 좋다. 아래의 예시처럼 사용한다면, Nest가 사용자의 커스텀 Exception을 인식하고 오류 응답을 자동으로 처리한다.

export class ForbiddenException extends HttpException {
  constructor() {
    super('Forbidden', HttpStatus.FORBIDDEN);
  }
}
@Get()
async findAll() {
  throw new ForbiddenException();
}

 

또한 아래와 같은 내장 HttpException을 제공한다.

 

 

Exception Filter

Nest는 앞에서 언급한 것 처럼, 예외 필터 레이어가 있어서 원하는 대로 예외를 다룰 수 있다.

로그를 남기거나, 에러 메세지를 커스터마이징 할 수 있다.

 

아래의 예시는, 상태 코드 뿐 아니라, 에러 발생 시각과 에러가 발생된 요청 URL을 반환한다.

import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    const status = exception.getStatus();

    response
      .status(status)
      .json({
        statusCode: status,
        timestamp: new Date().toISOString(),
        path: request.url,
      });
  }
}

 

@Catch(HttpExceptioin) 데코레이터는 필요한 메타데이터를 Exception Filter에 바인딩하여 Nest에게 이 특정 필터가 위의 내장 HttpException 유형의 예외를 찾고 있으며, 다른 예외는 없음을 알려준다. 또한 쉼표로 구분지어 여러 유형의 예외에 대한 필터를 한 번에 설정할 수 있다.

모든 Custom Exception Filter는 일반 ExceptionFilter interface를 구현해야 한다.
위 Custom Filter에 구현된 catch 메서드를 제공해야 한다는 뜻이다.

또한 @nestjs/platform-fastfy를 사용하는 경우, response.json() 대신 response.send()를 사용할 수 있다.

 

필터는, @UseFilters 데코레이터를 이용하여 적용시켜주면 되는데, 이 때 원하는 엔드포인트에 사용할 수 있다.

@Post()
@UseFilters(new HttpExceptionFilter())
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}

@Controller('user')
@UseFilters(HttpExceptionFilter)
export class UserController

 

애플리케이션 전역에서 작용하는 Filter로 설정하기 위해서는 main.ts 파일 안에서 다음과 같이 useGlobalFilters() 메서드를 사용한다.

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter());
  await app.listen(3000);
}
bootstrap();

 

전역 범위의 필터는 애플리케이션 전역에 대해 동작하기 때문에, 아래와 같이 모든 모듈에서 직접 전역 범위 필터를 등록할 수 있게 설정해주어 종속성 주입 문제를 해결해주어야 한다.

import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_FILTER,
      useClass: HttpExceptionFilter,
    },
  ],
})
export class AppModule {}

 

 

 

 

사용해보기

사내 백엔드 환경과 동일하게 셋팅한 후, 가볍게 복리계산 로직을 만들고, React로 간단하게 View를 만들어서 확인해보았다.

 

전역에서 사용할 것이기 때문에 위 포스팅의 전역 설정을 모두 마친 뒤, 아래처럼 코드를 작성했다.

async calculate(price: number, percent: number, time: number, type: string) {
    if (time <= 0 || price <= 0) return null;

    let priceArr = [price];
    let data = new Array<CPResponse>();
    let totalRevenue = 0;
    for (let i = 1; i <= time; i++) {
      const beforePrice = +priceArr[i - 1];
      const interest = beforePrice * (+percent / 100);
      const currentPrice = Math.round(beforePrice + interest);
      const revenue = currentPrice - beforePrice;
      const totalPercent = ((currentPrice - price) / (price / 100)).toFixed(2);
      totalRevenue += revenue;

      if (revenue === Infinity || !Number(revenue) || currentPrice === Infinity) {
        throw new BadRequestException();
      }

      priceArr.push(currentPrice);
      const cpResponse = new CPResponse(i + type, revenue, currentPrice, totalPercent + '%');
      data.push(cpResponse);
    }

    return { data, totalRevenue };

 

아래 부분에서, 숫자가 기하급수적으로 커질 때 Infinity 값이 출력될 수 있으므로, 수의 범위를 벗어나면(?) BadRequest를 반환하도록 했다.

if (revenue === Infinity || !Number(revenue) || currentPrice === Infinity) {
	throw new BadRequestException();
}

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[Docker] 도커 권한 문제 해결하기 / permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock:

Tech/트러블슈팅 2023. 9. 29. 00:25
728x90
728x90

 

에러 발생 경위

 

도커라이징하여 EC2 인스턴스로 배포를 계속 시도해보다가, 용량문제로 아래와 같은 에러가 발생했다.

failed to register layer: write /usr/src/app/node_modules/sockjs/Changelog: no space left on device

 

단순 인스턴스의 공간 문제인 것 같아 아래 명령어를 통해 디스크 용량을 확인해보았다.

$ df -h

 

용량을 거의 다 차지하고 있는 것 같아서, 도커 이미지를 여러번 등록하기만 했지 삭제하지 않았던 것이 생각나서 확인해보았더니 이미지가 엄청 많았다.

 

하나하나 일일이 IMAGE ID로 지워주기 힘들 것 같아 태그네임이 <none>인 모든 이미지파일을 지우는 명령어를 알려달라고 GPT에게 부탁했더니 아래 명령어를 던져주었고, 실행했더니 permission denied가 발생했다.

$ sudo docker rmi $(docker images -f "dangling=true" -q)

 

 

 

 

해결

# docker group을 생성 (이미 있었음)
$ sudo groupadd docker

# 현재 유저(ubuntu)를 docker group에 추가
$ sudo usermod -aG docker $USER

# 그룹 변경 명령어. 혹은 도커 로그아웃, 로그인 진행
$ newgrp docker

 

 

잘 삭제 되었다.

 

 

참고로 위에서 GPT에게 받은 dangling된 이미지를 지우는 명령어는 prune으로 족하다고 한다.

$ docker image prune
WARNING! This will remove all dangling images.
Are you sure you want to continue? [y/N] y
Deleted Images:
deleted: sha256:f7c5c7a3bbf08fbaefe57672bb3d7eb0f6c4a60fc2bd303ede54aacf29031ad3
.....
deleted: sha256:d7d5f3f6791131da7f70f52b086f0acec373e5c6f0d66d7fbcd5276b47468e28

Total reclaimed space: 17.29MB

 

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[NestJS] Docker와 Git Actions, EC2를 활용한 CI/CD 환경 구축 - 도커라이징하여 EC2에 배포하기

Tech/NodeJS 2023. 9. 22. 14:43
728x90
728x90

 

 

서론

사내 서비스가 Git Actions, Docker, EC2, PM2를 활용해 CI/CD 및 파이프라인을 구축하여 사용하고 있었기에 학습이 필요하다..

 

간단하게 따라해볼 무언가를 찾아서 따라해보면서 학습할 것이 필요했는데

 

초행이다보니 같은 환경에서 진행한 분을 찾기 어려워 외국 유튜브 영상이나 기타 해외 자료들을 활용하며 따라하며 공부했다.

 

 

 

 

CI/CD란?

Continuos Integration / Continuous Delivery의 약자로 단어 뜻대로 지속적 통합과 배포를 뜻한다.

 

 

 

 

 

DockerFile 작성

Docker에 이미지를 빌드하고 컨테이너 생성을 하는 과정은 단순하고 Nodejs docs에서도 가이드라인을 제시해준다.

https://nodejs.org/ko/docs/guides/nodejs-docker-webapp

 

Node.js 웹 앱의 도커라이징 | Node.js

Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.

nodejs.org

하지만 netsjs dockerfile을 사용하고 싶어 검색하여 사용하였다..

https://www.tomray.dev/nestjs-docker-production

 

Ultimate Guide: NestJS Dockerfile For Production [2022]

Learn how to write a Dockerfile that creates a production optimized image using the NodeJS Alpine image and multistage builds.

www.tomray.dev

 

나는 예제에다 alpine을 추가하여 사용했는데, 알파인은 클라우드 환경을 고려한 가벼운 리눅스 이미지다.

 

실습을 진행할 것이기 때문에 알파인으로 사용하였다.

DockerFile

# Base image
FROM node:18-alpine

# Create app directory
WORKDIR /usr/src/app

# A wildcard is used to ensure both package.json AND package-lock.json are copied
COPY package*.json ./

# Install app dependencies
RUN npm install

# Bundle app source
COPY . .

# Creates a "dist" folder with the production build
RUN npm run build

# Start the server using the production build
CMD [ "node", "dist/main.js" ]

 

.dockerignore

Dockerfile
.dockerignore
node_modules
npm-debug.log
dist

 

 

CI를 위한 설정파일 생성

github actions은 CI를 역할을 수행하는 강력한 도구중 하나이다.

github actions을 사용하기 위해 깃헙 내 레포에서 Actions에서 워크플로우를 생성했다.

git-action.yml

name: Docker Image CI

on:
  push:
    branches: [ "main" ]

jobs:

  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3
    - name: Login Dockerhub
      env:
        DOCKER_USERNAME: ${{secrets.DOCKERHUB_USERNAME}}
        DOCKER_PASSWORD: ${{secrets.DOCKERHUB_PASSWORD}}
      run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD      

    - name: Build the Docker image
      run: docker build -t manaweb-api .
    - name: taging
      run: docker tag manaweb-api:latest mag123c/manaweb-api:latest
    - name: Push to Dockerhub
      run: docker push mag123c/manaweb-api:latest

 

1. 메인 브렌치에 push될 때만 동작

2. 현재 레포의 코드를 체크아웃

3. 환경변수를 통한 도커 로그인

4. 도커 이미지 빌드

5. 푸시

 

환경변수는 내 깃허브 레포 > Secrets > Secrets and variable에서 변수 설정을 할 수 있다.

 

또한 여기서 푸시와 빌드 사이에  이미지 태깅에 대한 코드를 추가하였는데, 아래 트러블슈팅 1에서 다루었듯이 빌드 후 바로 푸시를 하게 되면 올바른 태그네임을 찾을 수 없다는 에러를 뱉어냈다.

 

 

 

Github Actions 동작 확인하기

계속된 실패로 지쳐있다가 태깅을 해주자 성공적으로 작동하는 모습이다.

 

 

 

AWS EC2 생성 및 Github Runner(self-hosted) 생성

ubuntu의 프리티어를 사용했으며 간단하게 인바운드 설정만 마친 뒤 인스턴스를 생성했다.

생성에 대한 포스팅은 생략하도록 하며 접속이 완료된 후 아래 명령어를 실행하자.

# root 계정으로 전환 + 현재 환경변수 사용
sudo su

# 패키지 버전 최신화
sudo apt update
sudo apt-get upgrade -y

 

깃허브의 레포에서 Settings - Actions - runner - New self-hosted runner에서 셋팅에 관련된 가이드라인을 받아볼 수 있다.

 

self-hosted의 가이드라인을 따라할 뿐이지만, 코드의 대략적인 기능을 파악해보았다.

#Download
## 1. 디렉토리 생성 및 이동
mkdir actions-runner && cd actions-runner
## 2. actions-runner 설치
curl -o actions-runner-linux-x64-2.309.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.309.0/actions-runner-linux-x64-2.309.0.tar.gz
## 3.설치한 파일의 SHA-256 해시를 계산하여 파일 체크
echo "2974243bab2a282349ac833475d241d5273605d3628f0685bd07fb5530f9bb1a  actions-runner-linux-x64-2.309.0.tar.gz" | shasum -a 256 -c
## 4.설치
tar xzf ./actions-runner-linux-x64-2.309.0.tar.gz

####################################################

#Configure
## 1. 내 레포와 연결. 액세스 권한이 있는 상태로 설정됨
./config.sh --url https://github.com/mag123c/manaweb-api --token A4Y6RDGK6BFAYHTP2KKLL2LFAXJFC
## 2. 러너 실행
./run.sh
### ./run.sh & : 백그라운드로 실행
### nohup ./run.sh : 세션이 종료되어도 runner 유지 (ssh 세션 끊어도 유지)

정상적으로 ubuntu ip의 Runners가 등록된 모습

 

마지막 명령어인 ./run.sh를 사용하여 실행시키면 아래와 같은 모습이 된다.

 

 

 

CD pipeline 생성

workflows에 CD를 위한 yml파일을 생성했다.

name: CD Pipeline

on:
  workflow_run:
    workflows: ["CI Pipeline"]
    types:
        - completed

jobs:

  build:

    runs-on: self-hosted

    steps:
    - name: Pull Docker image
      run: sudo docker pull mag123c/manaweb-api:latest
    - name: Delete Old docker container
      run: sudo docker rm -f manaweb-api-container || true
    - name: Run Docker Container
      run: sudo docker run -d -p 8080:8080 --name manaweb-api-container mag123c/manaweb-api

 

1. github actions에서 CI pipline이 동작이 완료되었을 때 CD 파이프라인이 실행되게 이름 및 타입 설정.

on:
  workflow_run:
    workflows: ["CI Pipeline"]
    types:
        - completed

 

 

2. runs-on은 작업이 실행될 환경을 지정한다. 깃헙 가이드라인에 따르면, yaml 파일에 self-hosted를 붙이라고 되어 있다.

 

3. 이미지를 다운로드(Pull)하고, 이전 컨테이너를 삭제한 다음 새로운 컨테이너를 실행한다.

 

CD 파이프라인은 CI 파이프라인이 성공적으로 동작이 완료되면 Docker 이미지를 다운로드하고 이를 사용하여 새로운 컨테이너를 실행하여 배포하는 역할을 한다. 이전 버전의 컨테이너는 삭제되고 새로운 버전이 설정한 포트를 통해 실행될 것이다.

 

 

 

EC2에 Docker 설치

https://docs.docker.com/engine/install/ubuntu/

 

Install Docker Engine on Ubuntu

Jumpstart your client-side server applications with Docker Engine on Ubuntu. This guide details prerequisites and multiple methods to install.

docs.docker.com

 

위 공식문서를 활용하여 설치를 진행해보자.

# Docker 공식 GPG Key 추가
## 1. 패키지 목록 업데이트
sudo apt-get update
## 2. 필요한 패키지 설치(인증, 통신)
sudo apt-get install ca-certificates curl gnupg
## 3. Docker 패키지 관리에 사용할 dir 생성
sudo install -m 0755 -d /etc/apt/keyrings
## 4. GPG Key 다운로드 + 파일 경로 및 파일명 지정
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# Docker Repo를 Apt 소스에 추가
## 1. Apt 저장소를 /etc/apt/sources.list.d/docker.list 파일에 추가
## 현재 ubuntu 버전의 코드명을 가져와서 사용
echo \
  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
## 2. Docker 패키지 사용을 위한 업데이트
sudo apt-get update
## 3. Docker 관련 패키지 설치 (도커엔진, 플러그인, compose 등)
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

 

설치가 완료되었으면 확인해보자.

# 현재 실행중인 Docker Container 목록 표시
sudo docker ps

정상적으로 찍히는 모습이다. (아직 실행중인 컨테이너가 없기 때문에 아무 값도 없다)

이후 docker login 명령어를 통해 도커에 로그인을 한다.

 

 

 

EC2에서 컨테이너 확인

Git Actions을 통해 CI/CD가 완료되고 나면 EC2에 정상적으로 컨테이너가 실행이 되어야 한다.

# 실행중인 docker container 확인
docker ps

하지만 명령어를 입력해도 실행되는것이 없어 5~6시간을 정상적으로 EC2에 배포가 되지 않은 것으로 착각했다.

 

# 실행중이거나 종료된 모든 컨테이너 확인
docker ps -a

모든 컨테이너를 확인해보니 아래처럼 컨테이너를 실행하자 마자 종료가 되는 모습이다.

해결한 과정은 아래 트러블슈팅의 2번 항목에서 확인할 수 있다.

 

 

5~6시간의 삽질 끝에 에러를 해결하고 나니 정상적으로 컨테이너가 실행되었다

 

curl 명령어를 통해 확인해보면 잘 동작한다.

또한 ec2의 public ip:port로 URL을 입력해도 잘 동작하는 모습.

 

 

이제 배포된 프로젝트의 API를 프론트에서 호출하여 사용하기만하면 끝!

 

 

 

 

 

 

트러블 슈팅

1. An image does not exist locally with the tag: [repo]/[image]

https://mag1c.tistory.com/466

 

[Git Actions / Docker] An image does not exist locally with the tag: [repo]/[image]

에러 메세지 An image does not exist locally with the tag: [repo]/[image] 아래와 같은 action.yml을 사용하고있었는데 계속해서 리파지토리에 이미지를 빌드한 후 태그를 못잡아 주는 것 같았다. name: Docker Image CI

mag1c.tistory.com

 

 

2. Docker Container 실행 후 바로 종료되는 현상

https://mag1c.tistory.com/469

 

 

 

 

참조

1. AWS EC2에 도커 설치하려다 에러 발생했을 때

https://boying-blog.tistory.com/82

 

ubuntu docker 설치시 Package 'docker-ce' has no installation candidate 해결

오랜만에 다른 서버에 도커를 설치할 일이 생겼는데 새 서버다 보니 이런 오류를 맞이했습니다 허허 Package 'docker-ce' has no installation candidate docker-ce 패키지를 사용할 수 없습니다. 하지만 다른 패

boying-blog.tistory.com

ssh를 password없이 restart할 때

$ sudo /etc/init.d/ssh restart

 

2. 'github action run after another' 키워드로 검색

https://stackoverflow.com/questions/62750603/github-actions-trigger-another-action-after-one-action-is-completed

 

Github Actions - trigger another action after one action is completed

I have one action (a yaml file) for deploying a docker image to Google Cloud Run. I would like to receive Slack or Email messages informing the build and push results. How could the message action be

stackoverflow.com

 

 

3. 궁금해서 찾아본 workflow 트리거들

https://docs.github.com/en/actions/using-workflows/triggering-a-workflow

 

Triggering a workflow - GitHub Docs

How to automatically trigger GitHub Actions workflows

docs.github.com

 

 

4. ubuntu에 docker 설치하기 (docker docs)

https://docs.docker.com/engine/install/ubuntu/

 

Install Docker Engine on Ubuntu

Jumpstart your client-side server applications with Docker Engine on Ubuntu. This guide details prerequisites and multiple methods to install.

docs.docker.com

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[Javascript] using : 자바스크립트의 새로운 변수 키워드

Tech/NodeJS 2023. 9. 18. 19:09
728x90
728x90

 

서론

즐겨보는 개발 관련 유튜브 중 하나인 노마드코더에서 흥미로운 영상을 게시했다.

 

기존 자바스크립트의 변수 const, var, let을 대체할 강력한 키워드인 using 이라고 소개했다.

역시 프로유튜버답게(??) 어그로를 잘 끄셔서 자연스레 정주행했다.

현재 자바에서 타입스크립트로 전향한 지금, 꽤나 중요한 이슈사항이 될 것 같아서 포스팅하여 정리해보려고 한다.

 

https://www.youtube.com/watch?v=-NmwyJ5S-IY&t=151s 

 

 

기존의 자바스크립트 변수

간단하게 기존 변수들에 대해 알아보자.

es6에서 포함된 기능중 하나는, 변수 선언에 사용할 수 있는 키워드인 let, const의 추가였다.

혹여 단순 using에 대한 정보만을 얻고자한다면, 스크롤을 많이 내려야 할 것 같다.

 

 

var의 문제점

기존의 var 변수가 함수 외부에서 선언될 때의 범위는 전역이고, 함수 내에서 선언될 때는 함수 범위 내로 지정된다.

var hi = "hey hi";

function test() {
    var hello = "hello";
}

//error: hello is not found
console.log(hello);

hello는 test() 함수 밖에서 사용할 수 없기 때문에 에러가 발생할 것이다.

 

또한 var 변수는 재선언이 가능하다.

var test = "test code"
var test = "test test"
var test2 = "test2 code"
test2 = "test2 test2"

 

var는 호이스팅 때문에 다음과 같은 코드를 작성하면 test is undefined를 발생시킨다.

console.log(test)
var test = "test"
//** test is undefined!!!!

//real action (Hoisting)
var test
console.log(test)
test = "test"

 

var의 이와같은 특징들을 활용하여 아래 코드를 살펴보자.

얼핏보면 괜찮은 var을 활용한 코드를 작성했다고 생각할 수 있다.

var test = "test"
var test2 = 10

if (test > 9) {
    var test = "hi hello"
}

console.log(test)
// >> "hi hello"

위 코드는 if문이 true기 때문에 test 변수는 재정의된다. 의도적으로 재정의한다면 괜찮겠지만, test 변수가 이미 정의되어 있다는 사실을 인식하지 못하는 경우에는 문제가 될 수 있다. 코드의 다른 부분에서 test 변수를 사용했다면 다른 출력값에 당황하게 될 수 있다.

 

요약해보면, var은 블록 스코프를 지원하지 않고, 재선언이 가능한 특성을 갖고 있다. 또한 호이스팅이 발생하기 때문에 가독성이 저하되고, 변수 선언 이전에 변수를 사용해도 오류가 발생하지 않는 등 많은 문제를 발생시킨다. 그리하여 const, let이 등장하게 되었다.

 

 

let

let으로 선언된 변수는 해당 블록 내에서만 사용이 가능하다.

let test = 'test'
let test2 = 10

if (test2 > 9) {
    let result = 'let is powerful'
    console.log(result) // 'let is powerful'
}
console.log(result) // result is not defined

result 변수가 정의된 블록 외부에서 해당 변수를 사용하면 에러가 발생된다.

 

또한 let은 업데이트는 가능하지만, 재선언은 불가능하다.

let test = 'test'
test = 'not test, its real'
let test = 'test'
let test = 'not test, its real'
// error: Identifier 'test' has already been declared

 

하지만, 다른 범위 내에서 재정의된다면 에러는 발생하지 않는다. 서로 다른범위를 가지므로 서로 다른 변수로 취급되기 때문이다.

let test = 'test'

if (true) {
    let test = 'its real test'
}
console.log(test) // 'its real test'

 

이처럼 let을 사용한다면, 변수가 범위 내에서만 존재하기 때문에, 이전에 이미 사용한 변수 명에 대해 더이상 신경쓰지 않아도 된다. 또한 범위 내에서 동일한 변수를 두 번 이상 선언할 수 없기 때문에 var의 문제가 발생하지 않는다.

 

또한 let의 경우, 호이스팅 시 초기화가 되지 않는다. 즉 호이스팅으로 선언은 되었지만 초기화가 되지 않았으므로 undefined의 값을 가지지 못한다. 그리하여 Reference Error를 뱉어낸다. test가 초기화되기 전에 사용되었기 때문이다.

console.log(test)
let test = "test"

//real action
let test
console.log(test) // Reference Error
test = "test"

 

여기서 계속 말하는 선언과 초기화는 다음 정의를 가진다.
선언(Declaration) : 스코프와 변수 객체 생성. 스코프가 변수 객체를 참조
초기화(Initialization) : 변수 객체 값을 위한 공간을 메모리에 할당된다 (undefined)

 

 

 

const

단어의 뜻에서도 알 수 있듯이 상수값을 유지하는 변수이다. 당연히 업데이트도, 재선언도 불가능하다.

const 또한 let처럼 선언된 블록 범위에서만 접근이 가능하다.

const test = 'i am constants'
test = 'change' //error: Assignment to constant variable
const test = 'i am constants'
const test = 'change' // error: Identifier 'test' has already been declared

 

하지만 객체의 프로퍼티의 "값"은 변경이 가능하다.

const test = {
    name: 'constants'
    age: 1
}

test.name = 'change'

 

const의 호이스팅도 let과 마찬가지다.

 

 

요약

구분 var let const
범위 전역 블록 범위 블록 범위
재할당 O O X
재선언 O X X
호이스팅 시 초기화여부 O X X
선언과 초기화 초기화없이 선언 가능 초기화없이 선언 가능 선언단계에서 초기화 필수

 

 

참조

https://www.youtube.com/watch?v=-NmwyJ5S-IY&t=151s 

https://letsusetech.com/introducing-javascripts-new-using-keyword-for-variables

https://www.freecodecamp.org/korean/news/var-let-constyi-caijeomeun/

 

 

 

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[Docker] Container 실행하자마자 exit 되는 경우 / docker ps 아무것도 없을 때

Tech/트러블슈팅 2023. 9. 17. 09:48
728x90
728x90

 

 

 

에러 상황

EC2, Github Actions, Docker을 통한 CI/CD 파이프라인 구축 실습 중 발생한 상황이다.

Github의 Action runner를 활용하여 CD를 수행했을 때 실행중인 컨테이너에 아무것도 없는 모습이다.

 

해결

1. 아래 명령어를 사용해 모든 컨테이너 목록을 조회했다.

docker ps -a

 

분명 실행은 되었지만 종료된 모습이다.

 

 

2. 로그를 찍어 보니 해당 에러를 확인할 수 있었다.

docker logs [컨테이너 NAMES]

 

나의 경우 Dockerfile의 마지막 줄에 실행 명령어로 node dist/main.js을 사용하였는데 해당 모듈을 찾을 수 없다는 에러였다.

 

 

해결

Dockerfile에서, 설정 경로와 카피 경로를 다시 설정하고, 실행 명령어를 바꿔주었더니 동작하더라.

FROM node:18-alpine
ENV PORT=8080

WORKDIR /usr/src/app
COPY . .

COPY package*.json .
RUN npm install

EXPOSE $PORT

CMD npm run start;
#CMD [ "node", "./dist/main.js" ]

 

차후에는 루트에서 module을 하나 만들어, 해당 모듈으로 실행되게 하는 방식으로 변경해보아야겟다.

 

 

 

 

 

참조

https://godbell.tistory.com/37

 

2021-02-11 AWS EC2 서버에 Docker Image 받고 실행하기

EC2 Ubuntu 서버에 Docker 설치하기Docker Hub에 게시한 Image 받기SFTP로 EC2에 파일 전송Docker Image 수정실행 직후 종료되는 Container참고 자료 Docker를 처음 사용해 보면서 다양한 문제를 겪었다. 본 항목에

godbell.tistory.com

https://velog.io/@swhybein/Docker-%EC%8B%A4%ED%96%89%ED%95%98%EC%9E%90%EB%A7%88%EC%9E%90-exit-%EB%90%A0-%EA%B2%BD%EC%9A%B0

 

Docker - 실행하자마자 exit 될 경우

간단한 장고 백엔드 프로젝트와 그 환경을 이미지로 만들어서 도커 컨테이너를 실행할 때, 바로 exit 될 수 있습니다. docker ps 했을 때 목록에 docker ps -a를 실행할 docker log

velog.io

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

윈도우 SSH접속 시 pem 파일 권한 변경 (윈도우 chmod 400)

Tech/트러블슈팅 2023. 9. 16. 17:52
728x90
728x90

 

사내에서 mac os를 사용해서 개발을 하다가

 

집에서 해당 기술들을 학습한 후 토이프로젝트 적용을 시켜보려고 ec2를 실행시킬 일이 있어서 pem파일을 chmod 400을 통해 권한을 설정하려고 했는데 권한 설정이 불가능하여 접속이 되지 않았다.

 

 

이럴 때 아래와 같은 명령어를 사용하자.

 

 

반드시 CMD에서 사용해야하며

icacls.exe 경로+pem이름 뒤 명령어를 입력하면 된다

icacls.exe myec2.pem /reset
icacls.exe myec2.pem /grant:r %username%:(R)
icacls.exe myec2.pem /inheritance:r
728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[Git Actions / Docker] An image does not exist locally with the tag: [repo]/[image]

Tech/트러블슈팅 2023. 9. 15. 18:46
728x90
728x90

 

 

 

에러 메세지

An image does not exist locally with the tag: [repo]/[image]

 

 

아래와 같은 action.yml을 사용하고있었는데 계속해서 리파지토리에 이미지를 빌드한 후 태그를 못잡아 주는 것 같았다.

name: Docker Image CI

on:
  push:
    branches: [ "main" ]

jobs:

  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3
    - name: Login Dockerhub
      env:
        DOCKER_USERNAME: ${{secrets.DOCKERHUB_USERNAME}}
        DOCKER_PASSWORD: ${{secrets.DOCKERHUB_PASSWORD}}
      run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD      

    - name: Build the Docker image
      run: docker build -t cicd-test .
    - name: Push to Dockerhub
      run: docker push mag123c/cicd-test:latest

 

 

 

해결

    - name: Build the Docker image
      run: docker build -t cicd-test .
   # 추가
   - name: taging
      run: docker tag cicd-test:latest mag123c/cicd-test:latest
      
    - name: Push to Dockerhub
      run: docker push mag123c/cicd-test:latest

 

 

 

 

 

참조

https://kb.vmware.com/s/article/54792

 

"An image does not exist locally with the tag" error when pushing images in VIC appliance (54792)

Pushing images to the VIC Appliance registry fails with the message "An image does not exist locally with the tag". Docker client output might look li

kb.vmware.com

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

방명록