-
Notifications
You must be signed in to change notification settings - Fork 1
[개별 멘토링] 2024.11.04
-
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 를 사용 했는가 명확한 근거를 가지고 공부해야 할 것 같다.
- [FE] 프론트엔드 기술스택
- [FE] 라이브러리 없이 차트 구현 이유
- [FE] Canvas API 사용방법
- [FE] 네비게이션 바 애니메이션 구현
- [FE] Socket.io 사용방법
- [FE] Tanstack Router에 대하여...
- [FE] Intl(Internationalization) API
- [FE] React Suspense 적용
- [FE] 한글 입력 방식의 유연성을 높인 검색 시스템 구현하기
- [BE] 백엔드 기술 스택
- [BE] SSE vs Socket.io
- [BE] Redis를 도입하게 된 계기
- [BE] ACG Rule을 활용한 Secure CI CD 파이프라인 구현
- [BE] Nginx 로드밸런싱을 통해 한국 투자 API 소켓 제한 극복
- [BE] 주가 지수 기능 개발 과정
- [BE] 매수 및 매도 기능 개발 과정
- [BE] 실시간 자산 조회 기능 개발 과정
- [BE] 단위 테스트
- [BE] redis를 이용한 한국투자 Open API 세션 관리
- [BE] 데이터베이스 인덱싱
- [FE] React에서의 DOM 요소 접근 (useRef vs getElementById)
- [FE] Outlet을 활용한 공통 레이아웃 관리
- [FE] react hooks가 특정 조건에서 실행되면 안되는 이유 & useQuery에 query function 매개변수가 undefined일 수도 있을 때 어떻게 해결할까
- [FE] cross‐domain 로컬 환경에서 cookie로 인증 처리하기 with vite proxy
- [FE] 크롬&사파리 Composition 차이
- [FE] useEffect 의존성 배열
- [BE] Naver Cloud Platform HTTPS 무응답 현상
- [BE] 한국투자 Open API에서 access token을 발급받지 못하는 문제
- [BE] 한국투자 Open API와 웹소켓 연결이 되지 않던 문제
- [BE] 한국투자 Open API 웹소켓 연결이 중단되는 문제
- [BE] 같은 주식 주문이 동시에 여러 번 체결되는 문제
- [BE] 한국투자 Open API Websocket 세션을 두 개에서 한 개로 변경하기
- [BE] Nginx 로드 밸런싱 중 Socket bad Request 발생하는 현상
- [BE] 매수/매도 체결 로직에 의해 redis pub/sub이 정상적으로 동작하지 않는 문제