Skip to content

[개별 멘토링] 2024.11.04

박진명 edited this page Nov 28, 2024 · 1 revision

이번 주 핵심 경험

  • Github Action을 이용한 CI/CD 구현

    이번 주차에 열심히 github action을 이용한 CI/CD 환경을 구축해보려 노력했습니다.

    • CI

      back/main이나 front/main 브랜치에 PR, push 이벤트 발생시에 lint, test,build를 진행하도록 action을 작성했습니다.

      name: CI
      
      on:
        push:
          branches: [back/main, front/main]
        pull_request:
          branches: [main,front/main, back/main, dev]
      
      jobs:
        BE-test-and-build:
          runs-on: ubuntu-latest
      
          steps:
            - uses: actions/checkout@v4
      
            - name: Set up Node.js
              uses: actions/setup-node@v4
              with:
                node-version: '20'
                cache: 'npm'
                cache-dependency-path: ./BE/package-lock.json
      
            - name: Install dependencies
              working-directory: ./BE
              run: npm ci
      
            - name: Run tests
              run: npm test
              working-directory: ./BE
              env:
                CI: true
      
            - name: Run linter
              working-directory: ./BE
              run: npm run lint
      
            - name: Build application
              working-directory: ./BE
              run: npm run build
      
        FE-test-and-build:
          runs-on: ubuntu-latest
      
          steps:
            - uses: actions/checkout@v4
      
            - name: Set up Node.js
              uses: actions/setup-node@v4
              with:
                node-version: '20'
                cache: 'npm'
                cache-dependency-path: ./BE/package-lock.json
      
            - name: Install dependencies
              working-directory: ./FE
              run: npm ci
      
            - name: Run linter
              working-directory: ./FE
              run: npm run lint
      
            - name: Build application
              working-directory: ./FE
              run: npm run build
      
    • deploy alpha 혹은 main 브랜치에 pull request 혹은 push 이벤트가 발생할 경우 네이버 클라우드 플랫폼에 존재하는 서버에 docker를 이용한 배포를 진행하고 있습니다.

      name: deploy
      
      on:
        push:
          branches: [main, alpha]
        pull_request:
          branches: [main, alpha]
      
      env:
        DOCKER_IMAGE: ${{ vars.DOCKERHUB_USERNAME }}/juga-docker
        DOCKER_TAG: ${{ github.sha }}
      
      jobs:
        build-and-deploy:
          runs-on: ubuntu-latest
      
          steps:
            - uses: actions/checkout@v3
      
            - name: Set up Node.js
              uses: actions/setup-node@v4
              with:
                node-version: '20'
                cache: 'npm'
                cache-dependency-path: ./BE/package-lock.json
      
            - name: Create .env file
              run: |
                touch ./BE/.env
                echo "${{ secrets.ENV }}" > ./BE/.env
      
            - name: Install dependencies
              working-directory: ./BE
              run: npm ci
      
            - name: Run tests
              working-directory: ./BE
              run: npm test
              env:
                CI: true
      
            - name: Run linter
              working-directory: ./BE
              run: npm run lint
      
            - name: Build application
              working-directory: ./BE
              run: npm run build
      
            - name: Login to Docker Hub
              uses: docker/login-action@v3
              with:
                username: ${{ vars.DOCKERHUB_USERNAME }}
                password: ${{ secrets.DOCKERHUB_TOKEN }}
      
            - name: Build and Push Docker Image
              working-directory: ./BE
              env:
                NCP_ACCESS_KEY: ${{ secrets.NCP_ACCESS_KEY }}
                NCP_SECRET_KEY: ${{ secrets.NCP_SECRET_KEY }}
              run: |
                docker build -t ${{ env.DOCKER_IMAGE }}:${{ env.DOCKER_TAG }} .
                docker tag ${{ env.DOCKER_IMAGE }}:${{ env.DOCKER_TAG }} ${{ env.DOCKER_IMAGE }}:latest
      
                docker push ${{ env.DOCKER_IMAGE }}:${{ env.DOCKER_TAG }}
                docker push ${{ env.DOCKER_IMAGE }}:latest
      
            - name: Get Github Actions IP
              id: ip
              uses: haythem/[email protected]
      
            - name: Setting NCP CLI & Credentials
              run: |
                cd ~
                wget https://www.ncloud.com/api/support/download/5/65
                unzip 65
                mkdir ~/.ncloud
                echo -e "[DEFAULT]\nncloud_access_key_id = ${{ secrets.NCP_ACCESS_KEY }}\nncloud_secret_access_key = ${{ secrets.NCP_SECRET_KEY }}\nncloud_api_url = ${{ secrets.NCP_API_URI }}" >> ~/.ncloud/configure
      
            - name: Add Github Action Ip to Security group
              run: |
                cd ~
                ls -la
                chmod -R 777 ~/cli_linux
                cd ~/cli_linux
                ./ncloud vserver addAccessControlGroupInboundRule --regionCode KR --vpcNo ${{ secrets.NCP_VPC_ID }} --accessControlGroupNo ${{ secrets.NCP_ACG_ID }} --accessControlGroupRuleList "protocolTypeCode='TCP', ipBlock='${{ steps.ip.outputs.ipv4 }}/32', portRange='${{ secrets.SSH_PORT }}'"
      
            - name: Deploy to NCP Server
              uses: appleboy/ssh-action@master
              with:
                host: ${{ secrets.NCP_SERVER_HOST }}
                username: ${{ secrets.NCP_SERVER_USERNAME }}
                key: ${{ secrets.NCP_SERVER_SSH_KEY }}
                port: 22
                script: |
                  docker system prune -af
                  echo "${{ secrets.ENV }}" > .env
                  
                  docker pull ${{ env.DOCKER_IMAGE }}:${{ env.DOCKER_TAG }}
                  docker stop juga-docker || true
                  docker rm juga-docker || true
                  docker run -d \
                    --name juga-docker \
                    -p 3000:3000 \
                    --env-file .env \
                    ${{ env.DOCKER_IMAGE }}:${{ env.DOCKER_TAG }}
      
            - name: Remove Github Action Ip to Security group
              run: |
                chmod -R 777 ~/cli_linux
                cd ~/cli_linux
                ./ncloud vserver removeAccessControlGroupInboundRule --regionCode KR --vpcNo ${{ secrets.NCP_VPC_ID }} --accessControlGroupNo ${{ secrets.NCP_ACG_ID }} --accessControlGroupRuleList "protocolTypeCode='TCP', ipBlock='${{ steps.ip.outputs.ipv4 }}/32', portRange='${{ secrets.SSH_PORT }}'"
  • NestJS

    • 의존성 주입

      NestJS가 가지는 강력한 기능인 의존성 주입 기능을 사용해보았습니다.

      nestJS는 @injectable 데코레이터를 이용해 프로바이더로 선언하고 주입 받을 곳에 생성자 주입 방식으로 선언하여 느슨한 방식의 결합을 진행합니다.

    • 미들웨어

      NestJS의 미들웨어는 Express의 미들웨어에서 조금 더 세분화 되어 있습니다.

      각각의 미들웨어는 자신의 역할 적용되는 단계가 존재합니다.

      middleware → guard → interceptor(before) → pipe → controller → service → controller → interceptor (after) → filter → client)

      이중에 저는 이번주에 사용자의 인증을 담당하는 guard와 데이터의 검증과 변환을 수행하는 pipe를 사용해 보았습니다.

      • guard

        이번 미션에서 저는 passport 라이브러리를 사용하여 PassportStrategy를 상속 받는 JWTStrategy 를 구현하였고 그리고 @nestjs/passport에 정의된 AuthGuard를 사용하였습니다. 프로바이더로 JWTStrategy를 구현하고

        export class JwtStrategy extends PassportStrategy(Strategy,'jwt') {
          constructor(
            @InjectRepository(UserRepository) private userRepository: UserRepository,
          ) {
            super({
              secretOrKey: process.env.JWT_SECRET_TOKEN,
              jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
            });
          }
        
          async validate(payload) {
            const { email } = payload;
            const user: User = await this.userRepository.findOne({ where: { email } });
            if (!user) throw new UnauthorizedException();
        
            return user;
          }
        }

        boolean형을 반환하는 validate 메소드를 통해 접근을 허용하거나 거부할 수 있습니다.

        이후 controller에서 UseGuard 데코레이터를 사용해주면 되고 AuthGuard의 파라미터로 Strategy에서 등록한 alias를 전달해주면 됩니다.

          @ApiOperation({ summary: 'Token 인증 테스트 API' })
          @Get('/test')
          @UseGuards(AuthGuard('jwt'))
          test(@Req() req: Request) {
            return req;
          }
        
      • pipe

        NestJS에서 파이프는 데이터의 검증과 변환에 사용됩니다.

        이번 주차에 저는 nestJS에서 제공하는 데코레이터를 이용해 오브젝트의 프로퍼티를 검증할 . 수있는 라이브러리인 class-validator와 NestJS의 빌트인 ValidatioPipe를 활용해 parameter-level에서 Request의 body가 유효한지 판단해 보았습니다.

        // autCredentials.dto.ts
        import {
          IsString,
          Matches,
          MaxLength,
          MinLength,
          IsEmail,
          ValidateNested,
          IsOptional,
        } from 'class-validator';
        import { ApiProperty } from '@nestjs/swagger';
        
        export class AuthCredentialsDto {
          @ApiProperty({
            description: '유저 이메일',
          })
          @IsString()
          email?: string;
        
          @ApiProperty({
            description: '유저 비밀번호',
          })
          @IsString()
          @MinLength(4)
          @MaxLength(20)
          @Matches(/^[a-zA-Z0-9]*$/, {
            message: '비밀번호는 영문과 숫자만 사용가능합니다',
          })
          password?: string;
        
          @ApiProperty({
            description: '카카오 ID',
          })
          @IsString()
          @IsOptional()
          kakaoId?: string;
        
          @ApiProperty({
            description: '카카오 액세스 토큰',
          })
          @IsString()
          @IsOptional()
          kakaoAccessToken?: string;
        }
        
        // auth.controller.ts
        
          @ApiOperation({ summary: '회원 가입 API' })
          @Post('/signup')
          signUp(@Body(ValidationPipe) authCredentialsDto: AuthCredentialsDto) {
            return this.authService.signUp(authCredentialsDto);
          }

개별 멘토링 기록

  • CI/CD에 대한 피드백

    위에 작성한 github action에 대한 내용에 대해 피드백을 주시면 감사하겠습니다!

    • 잘 되어 있는 것 같다.
    • FrontEnd 코드도 CI/CD 를 작성해보면 좋을 것 같다.
  • NestJS

    NestJS를 이번에 처음 사용해봅니다! 핵심 경험에도 여러가지 적어 보았지만 NestJS를 사용하면서 더 시도 해보면 좋을 만한 점들이 있을까요?

    • 순환 참조로 인해 같은 레이어에서 다른 service를 사용하면 안된다.
    • 완전 공통 로직의 경우 domain-service와 같이 sevice 레이어 아래에 repository 말고 domain-service라는 레이어를 다른 레이어를 만들어서 참조하게 하는게 좋을 것 같다.
    • 아래로 내려가는 구조를 잘생각 해야 한다. 역으로 올라가는 상황이 생기면 안된다.
    • nestjs 에서 제공하는 여러 middleware 를 다 활용해보는 것도 좋을 거 같다.
  • JWT 토큰

    이번 주차에 JWT 토큰을 사용해 로그인 기능을 구현해보았는데요. 처음 토큰을 생각할 때 제 구상은 accessToken과 refreshToken을 발급하고 refresh토큰은 DB에 저장해 accessToken이 만료되었을 때만 refresh Token을 요청 받는 거였는데 local storage에 저장하면 생기는 보안 이슈 때문에 local memory에 저장하는게 좋겠다는 의견이 나왔습니다. 이 경우 새로 고침 하면 access Token이 사라져 refresh를 요청하는데 이렇게 구현하는 게 괜찮은 방법일까요? 아니면 다른 방법이 있을까요?

    • session 기능을 사용했을 때와 어떤 차이가 존재하는지 명확하게 공부해보는게 좋을 것 같다.
    • 단순히 사용하는 것을 넘어 왜 jwt 를 사용 했는가 명확한 근거를 가지고 공부해야 할 것 같다.

📜 개발 일지

⚠️ 트러블 슈팅

❗ 규칙

🗒️ 기록

기획
회의록
데일리스크럼
그룹 멘토링
그룹 회고

😲 개별 멘토링

고동우
김진
서산
이시은
박진명
Clone this wiki locally