문제 상황

  • 프로젝트를 진행하면서 첫 로그인 시 이용약관 동의를 받아야 하는 상황이 생겼다.
  • 학교 관련서비스로 학교 로그인 api를 사용하기 때문에 학번을 id로 사용한다 . 그래서 학번을 기준으로 첫 로그인을 판단해야한다.
  • 현재 프로젝트가 백엔드리스 프로젝트로 존재했기 때문에 “어떻게 첫 로그인을 감지할까?”라는 문제상황이 있었다.

해결 방법

  • 현재 배포를 firebase를 github ci/cd와 붙여서 사용중인데, firestore를 사용해서 학번을 저장하는 방법으로 첫 로그인을 감지하자
  • firestore의 경우에는 콜렉션, 문서, 필드로 이루어진 noSql 형식의 db이다.

(첫 로그인 시)

  1. 로그인 시 firestore의 users 콜렉션을 확인하여 문서에 해당하는 학번이 존재하는지 확인한다.
  2. 존재하지 않으면 첫 로그인 이므로 약관 동의 페이지로 리다이렉션한다.
  3. 약관을 읽고 동의 버튼을 누르는 경우 firestore의 users 콜렉션에 학번을 기준으로 문서를 생성한다.

(두번 째 부터)

  1. 로그인 시 firestore의 users콜렉션에 학번이 존재하는 경우 메인페이지로 리다이렉션한다.

1. firestore생성

2. next.js 프로젝트와 연결

3. firestore의 문서 조회 및 생성으로 첫 로그인을 감지해 약관동의 페이지 리다이렉션 로직 구축

1. firebase의 firestore 추가 및 sdk 값 얻기

  • firebase에 firestore database를 해당하는 프로젝트 이름으로 추가한다.

  • 그 후 firebase에 웹앱을 추가하고 sdk값을 받는다.

2. 프로젝트에 firebase 추가

  • 프로젝트에 firebase를 추가해준다.
# npm 사용 시
npm install firebase
# yarn 사용 시
yarn add firebase

# typescript 사용 시 의존성 부여
npm install --save-dev @types/firebase
yarn add --save-dev @types/firebase
  • 위에서 얻었던 sdk값을 넣은 firebase.js(ts)파일을 루트 디렉토리에 만들어준다.
// 프로젝트 디렉토리의 root에
// firebase.js or firebase.ts
import { initializeApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';

const firebaseConfig = {
  apiKey: '',
  authDomain: '',
  projectId: '',
  storageBucket: '',
  messagingSenderId: '',
  appId: '',
  measurementId: '',
};

const app = initializeApp(firebaseConfig);
const db = getFirestore(app);

export { db };

⇒ 이제 firestore과 연결이 완료되어서 사용할 수 있다.

3. 로그인 시 약관동의 페이지로 리다이렉션하기

  • 우선 id값을 받아, 그 id가 firestore의 users 컬렉션 내부에 존재하는 문서값인지 확인하는 함수를 작성한다.
import { doc, getDoc, setDoc } from 'firebase/firestore';
import { db } from '../../../firebase';

export const checkStudentExists = async (
  studentId: string,
): Promise<boolean> => {
  const studentRef = doc(db, 'users', studentId);
  const docSnap = await getDoc(studentRef);
  return docSnap.exists();
};
  • 그런 후 기존의 로그인 로직에서 로그인 성공 시 위에서 작성한 함수를 사용해 약관동의를 받아야할 경우 약관동의 페이지로, 그렇지 않으면 메인페이지로 라우팅을 해준다.
  const handleLogin = async (id: string, password: string) => {
    try {
        .
        .
        . // 로그인 처리하는 로직들

            // firestore의 id값이 존재하는지 확인
      const studentExists = await checkStudentExists(id);
      if (studentExists === false) {
        router.replace('/tos'); // 존재하지 않을 경우 약관동의 페이지로 이동
      } else {
        router.replace('/'); // 존재할 경우 메인페이지로 이동
      }
      return data;
    } catch (err) {

    }

약관동의 시 firestore에 새로운 문서 생성

  • irestore의 users 콜렉션 아래에 id값으로 문서 생성하는 함수 작성
import { doc, getDoc, setDoc } from 'firebase/firestore';
import { db } from '../../../firebase';

export async function createStudent(
  studentId: string,
  userData: Record<string, boolean>,
): Promise<void> {
  const studentRef = doc(db, 'users', studentId);
  try {
    await setDoc(studentRef, userData, { merge: true });
  } catch (error) {
    console.error('Error writing document: ', error);
  }
}
  • 약관동의 버튼 클릭 시 위의 함수 사용해서 문서 id값, 그리고 내부 데이터를 {”isCheckTos”:true}로 새로운 문저 생성
const handleAgree = () => {
    const user = getUserInfo();
    createStudent(user.sId, { isCheckTos: true });
    router.push('/');
  };

⇒ 이렇게 될 경우 이제 최초 로그인시에만 약관동의 페이지가 떠오르고, 약관 동의를 한 후에는 다시 로그인한다해도 바로 메인페이지로 넘어가게 된다.

결과

1. 첫 로그인 시

2. 첫 로그인이 아닐 때 (약관동의 후)

firebase 프로젝트 생성

  • firebase 생성의 경우는 해당 글 참고
 

리액트와 파이어베이스 연결하기

파이어베이스 프로젝트 생성 제일 처음 파이어베이스를 연동해주어야 함. Sign in (클릭 시 파이어베이스로 이동) 파이어 베이스 접속 후 로그인. 프로젝트 추가 클릭 프로젝트 이름 입력 애널리

enochkon.tistory.com

리액트에 firebase cli 연결

  • 배포하고 싶은 리액트 프로젝트에 firebase cli 설치
npm install -g firebase-tools
  • firebase cli에 로그인
firebase login

  • 엔터 시 로그인 창 출력
  • 연결하고 싶은 계정을 선택한 후 엑세스 동의를 해주면 cli에 로그인이 완료된다.

계정 연결 창

hosting 서비스를 이용한 배포

  • firebase init
firebase init
  • init 후 배포를 하기 위해 Hosting : Configure files…. 로 내린 후 스페이스바로 선택한 후 엔터를 누른다.

  • 아까 firebase에서 만들어놓은 프로젝트를 선택하기 위해서 “Use an existing project”에서 엔터를 누른다.

  • 처음에 만든 프로젝트를 선택해준다.

  • 배포할 디렉토리를 선택한다.
    기본값은 public, react는 빌드 시 build에 생성되므로 build를 입력해준다.

  • 그 다음은 엔터를 눌러 계속 기본값으로 넘어가다가 “Set up automatic builds and deploy with Github?” 라는 질문에서 ci/cd 구축을 위해 yes를 입력해준다.
  • 그러면 깃허브 로그인 창이 떠오르게 된다.

  • 깃허브 로그인을 성공하면 ci/cd를 연결할 user/repository를 입력한다.

  • 그 후에는 계속 엔터를 쳐주다가 What is the name of the GitHub branch associated with your site's live channel?이라는 질문에서 배포할 브랜치 이름을 적어준다.

  • 그 다음 터미널에 뜬 url을 들어가서 해당 레포의 firebase CLI를 Revoke 해준다.
i  Action required: Visit this URL to revoke authorization for the Firebase CLI GitHub OAuth App:
https://github.com/settings/**********
  • 이렇게 되면 기본적인 설정이 완료되고 .github에 yml파일이 생기고 깃허브 레포에는 github action이 추가된다.

 

이 작업까지 완료하면 수동으로 직접 배포하는 것도 가능하다

npm run build

firebase deploy

 

github action을 사용한 ci/cd 연결

  • 기본적으로 firebase와 github를 연결하게 된다면 두가지의 yml 파일이 생기게 된다.
  1. firebase-hosting-merge.yml
    • 지정한 브랜치가 병합될 때 마다 자동으로 Firebase Hosting으로 배포
# This file was auto-generated by the Firebase CLI
# https://github.com/firebase/firebase-tools

name: Deploy to Firebase Hosting on merge
'on':
  push:
    branches:
      - main
jobs:
  build_and_deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: '${{ secrets.GITHUB_TOKEN }}'
          firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_PATHTOPET }}'
          channelId: live
          projectId: pathtopet
  1. firebase-hosting-Pull-Request
    • PR을 올릴 때 Firebase Hosting에 대한 미리보기 버전을 자동으로 배포하는 것
# This file was auto-generated by the Firebase CLI
# https://github.com/firebase/firebase-tools

name: Deploy to Firebase Hosting on PR
'on': pull_request
jobs:
  build_and_preview:
    if: '${{ github.event.pull_request.head.repo.full_name == github.repository }}'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: '${{ secrets.GITHUB_TOKEN }}'
          firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_PATHTOPET }}'
          projectId: pathtopet

yml파일에 대한 간단한 내용정리

  • name: 워크플로우의 이름
  • 'on': 이 워크플로우가 어떤 GitHub 이벤트에 의해 트리거될지 정의
  • jobs: 워크플로우에서 실행할 작업을 정의
    • if: 조건문으로, 이 작업이 실행될 조건을 지정
      여기서는 pull request가 현재 저장소에서 생성된 것인지 확인.
    • ${{ github.event.pull_request.head.repo.full_name == github.repository }} 이 조건은 pull request가 포크(fork)된 저장소가 아닌 원본 저장소에서 생성됐을 때만 작업을 실행하도록 한다.
    • runs-on: 작업이 실행될 환경을 지정
    • steps: 작업을 구성하는 단계들. 이 단계들은 순차적으로 실행
      • actions/checkout@v4: GitHub Action이 현재 저장소의 코드를 체크아웃하여 작업 실행 환경에 가져온다.
      • FirebaseExtended/action-hosting-deploy@v0: Firebase Hosting으로 배포를 담당하는 GitHub Action
  • 여기서 조금 변경해서 빌드를 자동으로 한 후 그것을 배포하게 코드를 약간 바꿔준다.
name: Deploy to Firebase Hosting on merge

"on":
  push:
    branches:
      - main

jobs:
  build_and_deploy:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v2
        with:
          node-version: "21"

      - name: Install Dependencies
        run: npm install 

      - name: Build
        run: npm run build 
        env:
          CI: false

      - uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: "${{ secrets.GITHUB_TOKEN }}"
          firebaseServiceAccount: "${{ secrets.FIREBASE_SERVICE_ACCOUNT_PATHTOPET }}"
          channelId: live
          projectId: pathtopet
name: Deploy to Firebase Hosting on PR

"on": pull_request

jobs:
  build_and_preview:
    if: "${{ github.event.pull_request.head.repo.full_name == github.repository }}"
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v2
        with:
          node-version: "21" 

      - name: Install Dependencies
        run: npm install 

      - name: Build
        run: npm run build 
        env:
          CI: false

      - uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: "${{ secrets.GITHUB_TOKEN }}"
          firebaseServiceAccount: "${{ secrets.FIREBASE_SERVICE_ACCOUNT_PATHTOPET }}"
          projectId: pathtopet
  • 배포되기전 빌드를 자동으로 해주기 위해 node js를 사용해서 npm install 후 build 해준다.

 

이렇게 되면 잘 배포되는것을 확인할 수 있다.

콜백 함수란?

  • 다른 코드의 인자로 넘겨주는 함수
  • call 부르다,호출하다 + back 뒤돌아오다, 되돌다 의 합성어
  • → 되돌아 호출해달라.
  • 콜백 함수는 다른 코드(함수 또는 메서드)에게 인자로 넘겨줌으로써 그 제어권도 함께 위임

제어권

호출시점

var intervalId = scope.setInterval(func,delay[, param1,param2,...]);
  • setInterval 함수는 funcdelay를 필수 인자로 받으며 param은 선택적으로 func에서 사용할 매개변수를 전달
var count = 0;
var cbFunc = function () {
    console. log(count);
    if (++count > 4) clearInterval (timer);
}

var timer = setInterval(cbFunc,300);
  • 다음 함수는 cbFunc이라는 함수를 setInterval이라는 다른 코드(함수)의 인자로 넘겨준다 → 콜백함수
  • 이로 인해서 cbFunc를 실행하는 제어권을 setInterval이 가지게 된다
code 호출 주체 제어권
cbFunc(); 사용자 사용자
setInterval(cbFunc,300); setInterval setInterval

콜백함수의 인자

Array.prototype.map(callback[,thisArg])
callback: function(currentValue,index,array)
  • map 매서드는 첫 번째 인자callback 함수를 받고, 생략 가능한 두 번째 인자로 콜백함수 내부에서 this로 인식할 대상을 특정할 수 있다.
  • 두 번째 인자가 생략되면 전역객체가 바인딩.
var newArr2 = [10,20,30].map(function (index, currentValue) {
    console.log(index, currentValue);
    return currentValue + 5;
    });
console.log(newArr2);
//실행결과
// 10 0
// 20 1
// 30 2
// [5, 6, 7]

map 함수의 정의

  • map함수를 사용할 때 index와 currentValue의 위치를 바꿔서 사용하면 역할이 바뀌어 버린다,
    • function (currentValue, index)라고 사용하면 currentValue가 0,1,2 index가 10,20,30이 되어버린다.
  • 즉, 콜백 함수의 제어권을 넘겨받은 코드(map)는 콜백 함수를 호출할 때 인자에 어떤 값들을 어떤 순서로 넘길 것인지에 대한 제어권을 가진다.

this

  • 별도의 this를 지정하는 방식
Array.prototype.map = function(callback, thisArg) {
  var mappedArr = [];
  for (var i = 0; i < this.length; i++) {
    var mappedValue = callback.call(thisArg || window, this[i], i, this);
    mappedArr[i] = mappedValue;
  }
  return mappedArr;
};
  • 코드를 보았을때 thisArg 입력되지 않으면 전역객체(window)를 아니면 입력된 thisArg의 값을 지정
  • 메서드를 콜백 함수로 전달한 경우에는 단순히 “함수”로서만 호출된다.
var obj = {
  vals: [1, 2, 3],
  logValues: function(v, i) {
    console.log(this, v, i);
  },
};
obj.logValues(1, 2); // { vals: [1, 2, 3], logValues: f } 1 2
[4, 5, 6].forEach(obj.logValues); // Window { ... } 4 0
// Window { ... } 5 1
// Window { ... } 6 2
  • obj의 메서드를 콜백함수로 넘겨도 obj자체가 가는것이 아닌 함수로서만 전달되므로 this가 전역으로 바인딩 된다.

콜백 함수 내부의 this에 다른 값 바인딩 하기

  • 콜백함수 내부에서 this가 객체를 바인딩 하는 여러가지 방법

전통적인 방법

  • 메서드 내부에 this를 저장 후 사용.
var obj1 = {
  name: 'obj1',
  func: function() {
    var self = this;
    return function() {
      console.log(self.name);
    };
  },
};
var callback = obj1.func();
setTimeout(callback, 1000);

→ 실제로는 이렇게 사용하지 않음.

  • 콜백함수 내부에서 this를 사용하지 않는 경우
var obj1 = {
  name: 'obj1',
  func: function() {
    console.log(obj1.name);
  },
};
setTimeout(obj1.func, 1000);

→ 위보단 훨씬 좋지만, 작성한 함수를 this를 이용해 재활용할 수 없게 되어버림

 

  • bind()함수 사용하기
var obj1 = {
  name: 'obj1',
  func: function() {
    console.log(this.name);
  },
};
setTimeout(obj1.func.bind(obj1), 1000);

var obj2 = { name: 'obj2' };
setTimeout(obj1.func.bind(obj2), 1500);

콜백 지옥 벗어나기

  • 동기 : 현재 실행중인 코드가 완료된 후에야 다음코드를 실행하는 방식
    • CPU에 의해 즉시 처리가 가능한 코드 (연산 등의 시간이 많이 걸린다 하더라도)
  • 비동기 : 현재 실행중인 코드의 완료 여부와 무관하게 즉시 다음 코드로 넘어감
    • 별도의 요청, 실행 대기, 보류 등과 관련된 코드

콜백 지옥

예시코드

setTimeout(
  function(name) {
    var coffeeList = name;
    console.log(coffeeList);

    setTimeout(
      function(name) {
        coffeeList += ', ' + name;
        console.log(coffeeList);

        setTimeout(
          function(name) {
            coffeeList += ', ' + name;
            console.log(coffeeList);

            setTimeout(
              function(name) {
                coffeeList += ', ' + name;
                console.log(coffeeList);
              },
              500,
              '카페라떼'
            );
          },
          500,
          '카페모카'
        );
      },
      500,
      '아메리카노'
    );
  },
  500,
  '에스프레소'
);
  • 0.5초 주기마다 커피 목록을 수집하고 출력하는 코드
  • 너무 많은 콜백으로 인해서 들여쓰기가 많아 읽기가 힘들어졌다.

콜백지옥 해결하기

기명 함수 사용하기

var coffeeList = '';

var addEspresso = function(name) {
  coffeeList = name;
  console.log(coffeeList);
  setTimeout(addAmericano, 500, '아메리카노');
};
var addAmericano = function(name) {
  coffeeList += ', ' + name;
  console.log(coffeeList);
  setTimeout(addMocha, 500, '카페모카');
};
var addMocha = function(name) {
  coffeeList += ', ' + name;
  console.log(coffeeList);
  setTimeout(addLatte, 500, '카페라떼');
};
var addLatte = function(name) {
  coffeeList += ', ' + name;
  console.log(coffeeList);
};

setTimeout(addEspresso, 500, '에스프레소');
  • 코드의 가독성도 높이고, 순서도 위에서 아래로 내려간다.
  • 하지만 일회성 함수를 전부 변수에 할당하는 것은 비효율적인 일이다.

(ES6) Promise를 이용하기

new Promise(function(resolve) {
  setTimeout(function() {
    var name = '에스프레소';
    console.log(name);
    resolve(name);
  }, 500);
})
  .then(function(prevName) {
    return new Promise(function(resolve) {
      setTimeout(function() {
        var name = prevName + ', 아메리카노';
        console.log(name);
        resolve(name);
      }, 500);
    });
  })
  .then(function(prevName) {
    return new Promise(function(resolve) {
      setTimeout(function() {
        var name = prevName + ', 카페모카';
        console.log(name);
        resolve(name);
      }, 500);
    });
  })
  .then(function(prevName) {
    return new Promise(function(resolve) {
      setTimeout(function() {
        var name = prevName + ', 카페라떼';
        console.log(name);
        resolve(name);
      }, 500);
    });
  });
  • Promise에 인자로 넘겨주는 콜백 함수는 바로 실행되지만, 그 내부에 resolve, reject함수를 호출하는 구문이 있을 경우 둘 중 하나가 실행되기 전까지는 then 혹은 catch 구문으로 넘어가지 않는다.

(ES6) Generator

var addCoffee = function(prevName, name) {
  setTimeout(function() {
    coffeeMaker.next(prevName ? prevName + ', ' + name : name);
  }, 500);
};
var coffeeGenerator = function*() {
  var espresso = yield addCoffee('', '에스프레소');
  console.log(espresso);
  var americano = yield addCoffee(espresso, '아메리카노');
  console.log(americano);
  var mocha = yield addCoffee(americano, '카페모카');
  console.log(mocha);
  var latte = yield addCoffee(mocha, '카페라떼');
  console.log(latte);
};
var coffeeMaker = coffeeGenerator();
coffeeMaker.next();
  • Generator 함수는 next 메서드를 생성해서 가장 먼저 등장하는 yield에서 함수의 실행을 멈춘다.
  • 그리고 다시 next 메서드를 호출하면 멈췄던 부분에서 시작해서 다음에 등장하는 yield에서 함수실행을 멈춘다.

(ES7) Promise + Async/await

var addCoffee = function(name) {
  return new Promise(function(resolve) {
    setTimeout(function() {
      resolve(name);
    }, 500);
  });
};
var coffeeMaker = async function() {
  var coffeeList = '';
  var _addCoffee = async function(name) {
    coffeeList += (coffeeList ? ',' : '') + (await addCoffee(name));
  };
  await _addCoffee('에스프레소');
  console.log(coffeeList);
  await _addCoffee('아메리카노');
  console.log(coffeeList);
  await _addCoffee('카페모카');
  console.log(coffeeList);
  await _addCoffee('카페라떼');
  console.log(coffeeList);
};
coffeeMaker();
  • 비동기 작업을 수행하는 함수를 async의 키워드를 사용해서 생성하고, 비동기 작업이 필요한 위치에만 await를 표기하는 것만으로 비동기 작업 처리

 

기타

  • async/await 지옥이라는 것도 존재

async hell

 

 

[번역] 비동기 지옥(async hell) 탈출방법 — Steemit

비동기(async)는 콜백지옥(callback hell)에서 우리를 자유롭게했습니다. 하지만 이는 잘못된 사용으로 인해 비동기 지옥(async hell)의 탄생으로 이어졌습니다. 이 글에서는 async / await 이 무엇인지 설명

steemit.com

 

🚀NCP와 깃허브 액션을 통한 CI/CD 구현기🚀

  1. ncp에서 배포 할 ubuntu server 생성
  2. npc에서 docker image를 저장할 object storage와 container registry 생성
  3. vite+react+typscript의 프론트 레포에서 배포할 dockerfile 작성
  4. 배포를 위한 github action 등록

NCP에서 ubuntu server 생성

네이버 클라우드 계정에서 server 생성

  • 서버는 유지비를 생각해서 micro로 생성했다. (최초 결제수단 등록 시 1년간 하나의 micro 서버 무료)

  • 새로운 인증키를 생성 후 다운받아 둔다.

  • 새로운 ACG(Access control group, 보안 그룹)을 생성해준다.
    • 임시로 0.0.0.0/0으로 열어두었다.
    • 보안을 위해서는 특정 주소에서만 접근할 수 있게 지정해주면 좋다.

  • 서버 생성 후 “운영중”이 뜰때까지 기다린다.

  • 서버가 생성되었다면 접속을 위해 포트포워딩을 설정해주어야 한다.
  • “외부 포트”에 임의의 포트번호를 입력 후 추가를 해준다.

  • 서버에 연결하기 위한 비밀번호를 설정해준다.
  • 생성한 서버 클릭 후 “서버 관리 및 설정 변경” → “관리자 비밀번호 확인”

  • 아까 서버를 생성할 때 다운받아 놨던 .pem 파일을 넣은 후 “비밀번호 확인”클릭

  • 그렇게 되면 관리자 이름과 비밀번호가 뜨게 되는데, 최초 접속 시 비밀번호로 복사해서 기록해 놓는다.
    • 보통 관리자 이름은 root다.

 

생성한 서버에 접속하기

  • 위에서 만든 서버에 접속해 본다.
  • “포트포워딩” 탭에서 설정했던 포트번호와, 거기에 있는 공인 ip를 활용해서 서버에 접속한다.
  • ssh 연결 시 비밀번호를 입력해달라고 하는데, 바로 위에서 복사해두었던 최초 접속 시 비밀번호를 넣어준다.
ssh -l root -p [포트번호] [공인 ip]
  • 접속 완료

  • 서버에 접속했으면 배포 시 사용할 도커를 설치해준다.

우분투 서버에 도커 설치

1. 우분투 시스템 패키지 업데이트

sudo apt-get update

2. 필요한 패키지 설치

sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common

3. Docker의 공식 GPG키를 추가

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

4. Docker의 공식 apt 저장소를 추가

sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"

5. 시스템 패키지 업데이트

sudo apt-get update

6. Docker 설치

sudo apt-get install docker-ce docker-ce-cli containerd.io

7. Docker가 설치 확인

7-1 도커 실행상태 확인

sudo systemctl status docker

7-2 도커 실행

sudo docker run hello-world

출처

[Docker] Ubuntu 22.04 Docker 설치

 

Object Storage, Container Registry 생성

  • 도커 이미지를 저장하고 가져다 쓰기 위한 Object Storage와 Container Registry를 생성해준다.

Object Storage 생성

  • Object Storage 이용 신청
  • 이용 신청 후 버킷 생성

  • 이름 입력 후 설정 건드리지않고 “다음” 버튼 클릭 후 버킷 생성



Container Registry 생성

  • Container Registry로 이동 후 “레지스트리 생성” 클릭

  • 이름 입력 및 생성한 Object Storage 넣은 후 생성



vite+react+typescript에서 Dockerfile 만들기

  • 배포할 프로젝트 최상단에 nginx를 사용하여 배포하는 Dockerfile을 생성
  • (참고로 현재 배포에 사용하는 gihub repo가 client와 server둘다 있어서 프론트 코드는 /client에 들어가 있다.)

# 첫 번째 단계: 빌드 환경 구축
FROM node:lts-alpine as build-stage

WORKDIR /app

# 의존성 파일 복사 및 설치
# COPY [내 package.json 파일과 yarn.lock 파일의 경로] [복사할 위치]
COPY ./client/package.json ./client/yarn.lock ./
RUN yarn install && yarn global add typescript

# 소스 코드 복사
COPY ./client .

RUN ls -l
# 애플리케이션 빌드
RUN yarn build

# 두 번째 단계: Nginx를 사용하여 애플리케이션 서빙
FROM nginx:stable-alpine as production-stage

# 빌드된 파일을 Nginx 서버로 복사
COPY --from=build-stage /app/dist /usr/share/nginx/html

# 80 포트 노출
EXPOSE 80

# Nginx 실행
CMD ["nginx", "-g", "daemon off;"]

 

CLI로 container registry에 도커 이미지 build,push 하기

NCP container registry에 로그인하기 위해서는 마이페이지에서 인증키 생성이 필요하다.

  • 마이페이지 - 계정관리 - 인증키관리 - 신규 API 인증키 생성 - Access Key ID, Secret Key가 각각 ID, PW로 로그인시 사용된다.
  • 해당 repository에 접근하기 위해서는 로그인이 필요하다.
  • Registry URL은 Container Registry에서 생성한 레지스트리를 클릭하면 Public Endpoint가 있는데 그것을 사용해주면 된다.
docker login <CONTAINER_REGISTRY_URL>
  • 다음을 입력하면 usernamepassword를 입력하라고 나오는데, 위에서 발급한 Access Key ID를 username으로, secret key를 password로 입력하면 된다.
  • 도커 파일을 작성한 배포할 프로젝트의 최상단에서 다음 명령어를 쳐주면 build되고 push되어서 Container Regisrty에 반영된다.
docker build -t <CONTAINER_REGISTRY_URL>/<TARGET_IMAGE[:TAG]> .
docker push <CONTAINER_REGISTRY_URL>/<TARGET_IMAGE[:TAG]>

 

깃허브 액션을 이용해 ci/cd 구축

깃허브 액션을 위한 .yml 파일 작성

  • 이제 ci/cd 구축을 위해 특정 브랜치가 push, merge 등 업데이트 될 때 마다 build 후 배포되게 깃허브 액션으로 등록 한다.
  • 레포에서 .github/workflows에 deploy.yml 파일을 생성한다.
name: auto deploy #작업 이름

on:
  push:
    branches:
      - dev # dev branch가 push 될때 실행

jobs:
  docker_push_front: #도커파일에 작성된 내용을 바탕으로 이미지를 push한다.
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2

      - name: Login to NCP Container Registry
        uses: docker/login-action@v2
        with: #container registry에 연결
          registry: ${{ secrets.NCP_CONTAINER_REGISTRY }}
          username: ${{ secrets.NCP_ACCESS_KEY }}
          password: ${{ secrets.NCP_SECRET_KEY }}

      - name: Build and Push Docker Image
        uses: docker/build-push-action@v3
        with: # 도커파일에 작성된 내용으로 이미지를 빌드, container registry에 push한다.
          context: .
          file: ./client/Dockerfile  
          push: true
          tags: ${{ secrets.NCP_CONTAINER_REGISTRY }}/front
          cache-from: type=registry,ref=${{ secrets.NCP_CONTAINER_REGISTRY }}/front
          cache-to: type=inline

  docker_pull_front:
    name: Connect server ssh and pull frontend from container registry
    needs: docker_push_front #위의 push가 진행 된 후
    runs-on: ubuntu-latest
    steps: # container registrty에 push된 
      - name: connect ssh
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.FRONT_HOST }}
          username: ${{ secrets.FRONT_USERNAME }}
          password: ${{ secrets.FRONT_PASSWORD }}
          port: ${{ secrets.FRONT_PORT }}
          script: |
            docker pull ${{ secrets.NCP_CONTAINER_REGISTRY }}/front
            docker stop $(docker ps -a -q)
            docker rm $(docker ps -a -q)
            docker run -d -p 80:80 ${{ secrets.NCP_CONTAINER_REGISTRY }}/front
            docker image prune -f

 

깃허브 secret 등록

  • 깃허브 액션이 제대로 작동하기 위해서 serect을 등록한다.
  • github 레포에서 settings → secrets and variables → actions로 가서 “new repository secret”르 누른 후 다음과 같이 이름과 그 값을 추가한다.

컨테이너 레지스트리 연결할 때 사용하는 값들

  • NCP_CONTAINER_REGISTRY : 컨테이너 레지스트리의 Public Endpoint
  • NCP_ACCESS_KEY : mypage에서 발급 받은 Access Key ID
  • NCP_SECRET_KEY : mypage에서 발급 받은 secret key
  • FRONT_HOST : 포트포워딩 탭에서 설정했던 서버 접속 공인 ip
  • FRONT_PORT : 포트포워딩 탭에서 설정했던 포트번호
  • FRONT_USERNAME : 관리자 비밀번호 설정에서 확인했던 관리자 이름 (보통 초기에는 root)
  • FRONT_PASSWORD : 관리자 비밀번호 설정에서 확인했던 비밀번호

→ 추가 후 dev 브랜치에 push가 일어나게 되면 자동으로 server에 빌드 및 배포가 이루어진다.



서버 공인 ip 설정

  • 지금까지 사용했던 ip는 “서버 접속용 공인 ip”다.
  • 실제로 배포한 server에 접속하기 위해서는 공인 ip가 필요하다.
  • server의 public ip로 이동

  • 공인 ip 신청, 처음 만들었던 서버를 적용 서버로 선택 연결



완성

  • 이제 설정한 공인 ip로 접속할 경우 배포되어있는 것을 확인할 수 있다.

Reference

https://sungbin.dev/post/NCP Container Registry를 활용하여 CI·CD 환경 구축하기

파이어베이스 프로젝트 생성

제일 처음 파이어베이스를 연동해주어야 함.

Sign in (클릭 시 파이어베이스로 이동)

  • 파이어 베이스 접속 후 로그인.
  • 프로젝트 추가 클릭
  • 프로젝트 이름 입력
  • 애널리틱스 건들지 않고 넘어가기
  • 기존의 계정 혹은 새 계정 만들고 선택 후 프로젝트 생성

firebase 프로젝트에 웹앱 추가

  • 앱 추가 클릭 후 웹 클릭
  • 이름 입력 후 다음 클릭
![](https://velog.velcdn.com/images/fdsa200/post/d0fab8aa-6c0b-4f51-9021-2b00dd0c3506/image.png)
  • sdk 설정에 있는 js 코드 복사

리엑트에 파이어베이스 추가

위에서 웹을 추가할 때 봤던 sdk설정에 있는 내용에 리엑트에 추가

  • 파이어 베이스 설치
npm install firebase
  • 설치 후 firebase.js 파일 하나 만들어서 위의 내용 복사&붙여넣기 (파일이름은 상관없음)

+ Recent posts