NodeJS를 사용한 InnerLoop 개발

1. 개요

이 실습에서는 컨테이너화된 환경에서 NodeJS 애플리케이션을 개발하는 소프트웨어 엔지니어의 개발 워크플로를 간소화하도록 설계된 기능을 보여줍니다. 일반적인 컨테이너 개발에서는 사용자가 컨테이너와 컨테이너 빌드 프로세스에 대한 세부정보를 이해해야 합니다. 또한 개발자는 일반적으로 흐름을 중단하고 IDE에서 벗어나 원격 환경에서 애플리케이션을 테스트하고 디버그해야 합니다. 이 튜토리얼에서 언급한 도구와 기술을 통해 개발자는 IDE를 벗어나지 않고도 컨테이너화된 애플리케이션으로 효과적으로 작업할 수 있습니다.

학습할 내용

이 실습에서는 다음을 포함하여 GCP에서 컨테이너를 사용하여 개발하는 방법을 알아봅니다.

  • 시작 Nodejs 애플리케이션 만들기
  • 컨테이너 개발을 위한 Nodejs 애플리케이션 구성
  • 간단한 CRUD REST 서비스 코딩
  • GKE에 배포
  • 오류 상태 디버깅
  • 중단점 / 로그 활용 중
  • 변경사항을 GKE에 다시 핫 배포
  • 선택사항: 백엔드 지속성을 위한 CloudSQL 통합

2. 설정 및 요구사항

자습형 환경 설정

  1. Google Cloud Console에 로그인하여 새 프로젝트를 만들거나 기존 프로젝트를 재사용합니다. 아직 Gmail이나 Google Workspace 계정이 없는 경우 계정을 만들어야 합니다.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • 프로젝트 이름은 이 프로젝트 참가자의 표시 이름입니다. 이는 Google API에서 사용하지 않는 문자열이며 언제든지 업데이트할 수 있습니다.
  • 프로젝트 ID는 모든 Google Cloud 프로젝트에서 고유해야 하며, 변경할 수 없습니다(설정된 후에는 변경할 수 없음). Cloud Console은 고유한 문자열을 자동으로 생성합니다. 일반적으로 신경 쓰지 않아도 됩니다. 대부분의 Codelab에서는 프로젝트 ID를 참조해야 하며(일반적으로 PROJECT_ID로 식별됨), 마음에 들지 않는 경우 임의로 다시 생성하거나 직접 지정해서 사용할 수 있는지 확인하세요. 프로젝트가 생성되면 프로젝트 ID가 '고정'됩니다.
  • 세 번째 값은 일부 API에서 사용하는 프로젝트 번호입니다. 이 세 가지 값에 대한 자세한 내용은 문서를 참조하세요.
  1. 다음으로 Cloud 리소스/API를 사용하려면 Cloud Console에서 결제를 사용 설정해야 합니다. 이 Codelab 실행에는 많은 비용이 들지 않습니다. 이 튜토리얼을 마친 후 비용이 결제되지 않도록 리소스를 종료하려면 Codelab의 끝에 있는 '삭제' 안내를 따르세요. Google Cloud 새 사용자에게는 미화 $300 상당의 무료 체험판 프로그램에 참여할 수 있는 자격이 부여됩니다.

Cloud Shell 편집기 시작

이 실습은 Google Cloud Shell 편집기에서 사용할 수 있도록 설계 및 테스트되었습니다. 편집기에 액세스하려면

  1. https://console.cloud.google.com에서 Google 프로젝트에 액세스합니다.
  2. 오른쪽 상단에서 Cloud Shell 편집기 아이콘을 클릭합니다.

8560cc8d45e8c112.png

  1. 창 하단에 새 창이 열립니다.
  2. 편집기 열기 버튼을 클릭합니다.

9e504cb98a6a8005.png

  1. 편집기가 오른쪽에 탐색기가 표시되고 중앙에 편집기가 열립니다.
  2. 화면 하단에서 터미널 창도 사용할 수 있습니다.
  3. 터미널이 열려 있지 않으면 `ctrl+` 키 조합을 사용하여 새 터미널 창을 엽니다.

gcloud 설정

Cloud Shell에서 프로젝트 ID와 애플리케이션을 배포할 리전을 설정합니다. 이러한 변수를 PROJECT_IDREGION 변수로 저장합니다.

export PROJECT_ID=$(gcloud config get-value project)
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')

GKE 클러스터 및 데이터베이스 설정

  1. 설정 스크립트를 다운로드하고 실행 파일로 만듭니다.
wget https://raw.githubusercontent.com/GoogleCloudPlatform/container-developer-workshop/main/labs/nodejs/setup.sh
chmod +x setup.sh

이 실습에서 사용하는 인프라를 프로비저닝합니다.

이 실습에서는 GKE에 코드를 배포하고 Spanner 데이터베이스에 저장된 데이터에 액세스합니다. 아래 설정 스크립트가 이 인프라를 준비합니다.

  1. setup.sh 파일을 열고 현재 CHANGEME로 설정된 비밀번호 값을 수정하세요.
  2. 설정 스크립트를 실행하여 이 실습에서 사용할 GKE 클러스터와 CloudSQL 데이터베이스를 설정합니다.
./setup.sh
  1. Cloud Shell에서 이름이 mynodejsapp인 새 디렉터리를 만듭니다.
mkdir mynodejsapp
  1. 이 디렉터리로 변경하고 작업공간으로 엽니다. 이렇게 하면 새로 만든 폴더에 작업공간 구성이 생성되어 편집기가 새로고침됩니다.
cd mynodejsapp && cloudshell workspace .
  1. NVM을 사용하여 Node 및 NPM을 설치합니다.
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
        
        # This loads nvm bash_completion
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"  

nvm install stable

nvm alias default stable

3. 새로운 시작 애플리케이션 만들기

  1. 애플리케이션 초기화

다음 명령어를 실행하여 package.json 파일을 만듭니다.

npm init
    Choose the entry point: (index.js) src/index.js and default values for the rest of the parameters. This will create the file with following contents
{
  "name": "mynodejsapp",
  "version": "1.0.0",
  "description": "",
  "main": "src/index.js",,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}
  1. 진입점 추가

스크립트 "start": "node src/index.js",에 시작 명령어를 포함하도록 이 파일을 수정합니다. 변경 후 스크립트는 아래의 코드 스니펫과 같이 표시됩니다.

"scripts": {
    "start": "node src/index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  1. Express 종속 항목 추가

추가할 코드도 express를 사용하므로 이 package.json 파일에 종속 항목을 추가하겠습니다. 모든 변경사항이 적용된 후 package.json 파일은 아래와 같이 표시됩니다.

​​{
  "name": "mynodejsapp",
  "version": "1.0.0",
  "description": "",
  "main": "src/index.js",
  "scripts": {
    "start": "node src/index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Your Name",
  "license": "ISC",
  "dependencies": {
    "express": "^4.16.4"
  }
}
  1. index.js 파일 만들기

src라는 소스 디렉터리를 만듭니다.

다음 코드를 사용하여 src/index.js를 만듭니다.

const express = require('express');
const app = express();
const PORT = 8080;

app.get('/', (req, res) => {
    var message="Greetings from Node";
    res.send({ message: message });
  });

app.listen(PORT, () => {
  console.log(`Server running at: http://localhost:${PORT}/`);

});

PORT는 8080 값으로 설정되어 있습니다.

매니페스트 생성

Skaffold는 컨테이너 개발을 간소화하는 통합 도구를 제공합니다. 이 단계에서는 기본 kubernetes YAML 파일을 자동으로 생성하는 skaffold를 초기화합니다. 아래 명령어를 실행하여 프로세스를 시작합니다.

터미널에서 다음 명령어를 실행합니다.

skaffold init --generate-manifests

메시지가 표시되면 다음을 수행합니다.

  • 포트에 8080을 입력합니다.
  • y를 입력하여 구성을 저장합니다.

작업공간 시각화에 파일 두 개(skaffold.yamldeployment.yaml)가 추가되었습니다.

앱 이름 업데이트

구성에 포함된 기본값이 현재 애플리케이션의 이름과 일치하지 않습니다. 기본값이 아닌 애플리케이션 이름을 참조하도록 파일을 업데이트합니다.

  1. Skaffold 구성의 항목 변경
  • skaffold.yaml 열기
  • 현재 package-json-image(으)로 설정된 이미지 이름을 선택합니다.
  • 마우스 오른쪽 버튼을 클릭하고 '모든 어커런스 변경'을 선택합니다.
  • 새 이름을 mynodejsapp(으)로 입력합니다.
  1. Kubernetes 구성의 항목 변경
  • deployment.yaml 파일 열기
  • 현재 package-json-image(으)로 설정된 이미지 이름을 선택합니다.
  • 마우스 오른쪽 버튼을 클릭하고 '모든 어커런스 변경'을 선택합니다.
  • 새 이름을 mynodejsapp(으)로 입력합니다.

skaffold.yaml 파일에서 build 섹션은 buildpacks를 사용하여 애플리케이션을 컨테이너화합니다. 이 코드에는 Dockerfile이 없으며 개발자는 이 애플리케이션을 컨테이너화하는 데 Docker에 대한 지식이 필요하지 않습니다.

또한 이 Scaffold 구성에 의해 편집기와 실행 중인 컨테이너 간에 핫 동기화가 자동으로 사용 설정됩니다. 핫 동기화를 사용 설정하기 위해 추가 구성이 필요하지 않습니다.

4. 개발 과정 살펴보기

이 섹션에서는 Cloud Code 플러그인을 사용하여 기본 프로세스를 학습하고 시작 애플리케이션의 구성 및 설정을 검증하는 몇 가지 단계를 안내합니다.

Cloud Code는 Skaffold와 통합되어 개발 프로세스를 간소화합니다. 다음 단계에서 GKE에 배포하면 Cloud Code 및 Skaffold에서 자동으로 컨테이너 이미지를 빌드하여 Container Registry로 푸시한 다음 애플리케이션을 GKE에 배포합니다. 이 작업은 개발자 흐름에서 세부정보를 추상화하는 이면에서 발생합니다. 또한 Cloud Code는 컨테이너 기반 개발에 기존 디버그 및 핫동기화 기능을 제공하여 개발 프로세스를 개선합니다.

Kubernetes에 배포

  1. Cloud Shell 편집기 하단의 창에서 Cloud Code 를 선택합니다.

fdc797a769040839.png

  1. 상단에 표시되는 패널에서 Kubernetes에서 실행을 선택합니다. 메시지가 표시되면 '예'를 선택하여 현재 Kubernetes 컨텍스트를 사용합니다.

cfce0d11ef307087.png

  1. 명령어를 처음 실행하면 현재 Kubernetes 컨텍스트를 원하는지 묻는 프롬프트가 화면 상단에 표시되면 '예'를 선택합니다. 현재 컨텍스트를 수락하고 사용합니다.

817ee33b5b412ff8.png

  1. 그러면 어떤 Container Registry를 사용할지 묻는 프롬프트가 표시됩니다. 입력된 기본값을 수락하려면 Enter 키를 누르세요.

eb4469aed97a25f6.png

  1. 하단 창에서 출력 탭을 선택하여 진행 상황 및 알림을 확인합니다.

f95b620569ba96c5.png

  1. 'Kubernetes: Run/Debug - DETAILS(Kubernetes: 실행/디버그 - 세부정보)'를 선택합니다. 오른쪽에 있는 채널 드롭다운에서 클릭하여 컨테이너에서 라이브 스트리밍되는 추가 세부정보 및 로그를 볼 수 있습니다.

94acdcdda6d2108.png

  1. 'Kubernetes: Run/Debug(Kubernetes: 실행/디버그)'를 선택하여 간소화된 뷰로 돌아갑니다. 선택합니다.
  2. 빌드 및 테스트가 완료되면 출력 탭에 Resource deployment/mynodejsapp status completed successfully이 표시되고 URL이 나열됩니다. "Forwarded URL from service demo-app: http://localhost:8080"
  3. Cloud Code 터미널에서 출력의 URL (http://localhost:8080)에 마우스를 가져간 다음 표시되는 도움말에서 '웹 미리보기 열기'를 선택합니다.

응답은 다음과 같습니다.

{"message":"Greetings from Node"}

핫 리로드

  1. src/index.js로 이동합니다. 인사말 메시지의 코드를 'Hello from Node'로 수정합니다.

Output 창의 Kubernetes: Run/Debug 뷰에서 감시자가 업데이트된 파일을 Kubernetes의 컨테이너와 동기화하는 것을 즉시 확인할 수 있습니다.

Update initiated
File sync started for 1 files for gcr.io/myproject/mynodejsapp:latest@sha256:f554756b3b4d6c301c4b26ef96102227cfa2833270db56241248ae42baa1971a
File sync succeeded for 1 files for gcr.io/myproject/mynodejsapp:latest@sha256:f554756b3b4d6c301c4b26ef96102227cfa2833270db56241248ae42baa1971a
Update succeeded
  1. Kubernetes: Run/Debug - Detailed 뷰로 전환하면 파일 변경사항을 인식하고 노드를 다시 시작합니다.
files modified: [src/index.js]
Copying files:map[src/index.js:[/workspace/src/index.js]]togcr.io/myproject/mynodejsapp:latest@sha256:f554756b3b4d6c301c4b26ef96102227cfa2833270db56241248ae42baa1971a
Syncing 1 files for gcr.io/myproject/mynodejsapp:latest@sha256:f554756b3b4d6c301c4b26ef96102227cfa2833270db56241248ae42baa1971a
Watching for changes...
[mynodejsapp]
[mynodejsapp]> mynodejsapp@1.0.0 start /workspace
[mynodejsapp]> node src/index.js
[mynodejsapp]
[mynodejsapp]Server running at: http://localhost:8080/
  1. 업데이트된 결과를 보려면 브라우저를 새로고침하세요.

디버깅

  1. 디버그 뷰로 이동하여 현재 스레드 647213126d7a4c7b.png를 중지합니다.
  2. 하단 메뉴에서 Cloud Code를 클릭하고 Debug on Kubernetes를 선택하여 debug 모드로 애플리케이션을 실행합니다.
  • Output 창의 Kubernetes Run/Debug - Detailed 뷰에서 skaffold가 이 애플리케이션을 디버그 모드로 배포합니다.
  • 애플리케이션을 빌드하고 배포하는 데 몇 분 정도 걸립니다. 이번에는 디버거가 첨부됩니다.
Port forwarding pod/mynodejsapp-6bbcf847cd-vqr6v in namespace default, remote port 9229 -> http://127.0.0.1:9229
[mynodejsapp]Debugger attached.
  1. 하단 상태 표시줄의 색상이 파란색에서 주황색으로 변경되어 디버그 모드임을 나타냅니다.
  2. Kubernetes Run/Debug 뷰에서 디버그 가능한 컨테이너가 시작되었음을 확인합니다.
**************URLs*****************
Forwarded URL from service mynodejsapp-service: http://localhost:8080
Debuggable container started pod/mynodejsapp-deployment-6bc7598798-xl9kj:mynodejsapp (default)
Update succeeded
***********************************

중단점 활용

  1. src/index.js 열기
  2. var message="Greetings from Node";라는 문을 찾습니다.
  3. 줄 번호 왼쪽의 빈 공간을 클릭하여 해당 줄에 중단점을 추가합니다. 중단점이 설정되었음을 알리는 빨간색 표시기가 표시됨
  4. 브라우저를 새로고침하고 디버거가 중단점에서 프로세스를 중지하며 GKE에서 원격으로 실행 중인 애플리케이션의 변수와 상태를 조사할 수 있습니다.
  5. "message" 변수를 찾을 때까지 변수 섹션을 아래로 클릭합니다.
  6. Step over 7cfdee4fd6ef5c3a.png를 눌러 줄을 실행합니다.
  7. "Greetings from Node"로 변경되는 "message" 변수의 현재 값을 확인합니다.
  8. 변수 이름 'target'을 더블클릭합니다. 팝업에서 값을 "Hello from Node"와 같은 다른 값으로 변경합니다.
  9. 디버그 제어판에서 계속 버튼을 클릭합니다.
  10. 브라우저에서 응답을 검토합니다. 이제 방금 입력한 업데이트된 값이 표시됩니다.
  11. '디버그' 중지 중지 버튼 647213126d7a4c7b.png을 눌러 모드를 종료하고 중단점을 다시 클릭하여 중단점을 삭제합니다.

5. 간단한 CRUD REST 서비스 개발

이제 애플리케이션이 컨테이너화된 개발을 위해 완전히 구성되었으며 Cloud Code를 사용하여 기본 개발 워크플로를 살펴봤습니다. 다음 섹션에서는 Google Cloud의 관리형 데이터베이스에 연결하는 REST 서비스 엔드포인트를 추가하여 학습한 내용을 연습합니다.

종속 항목 구성

애플리케이션 코드는 데이터베이스를 사용하여 나머지 서비스 데이터를 유지합니다. package.json 파일에 다음을 추가하여 종속 항목을 사용할 수 있는지 확인합니다.

  1. package.json 파일에 종속 항목 pgsequelize를 두 개 더 추가하여 CRUD 애플리케이션 Postgres를 빌드합니다. 변경사항을 게시하면 종속 항목 섹션이 다음과 같이 표시됩니다.
    "dependencies": {
    "express": "^4.16.4",
    "pg": "^8.7.3",
    "sequelize": "^6.17.0"
  }

REST 서비스 코딩

  1. 이 애플리케이션에 CRUD 애플리케이션 코드 추가
wget -O app.zip https://github.com/GoogleCloudPlatform/container-developer-workshop/raw/main/labs/nodejs/app.zip

unzip app.zip

이 코드에는

  • item의 항목 모델이 있는 models 폴더
  • CRUD 작업을 수행하는 코드가 포함된 controllers 폴더
  • 특정 URL 패턴을 다른 호출로 라우팅하는 routes 폴더
  • 데이터베이스 연결 세부정보가 있는 config 폴더
  1. db.config.js 파일의 데이터베이스 구성은 데이터베이스에 연결하기 위해 제공해야 하는 환경 변수를 나타냅니다. 또한 URL 인코딩을 위해 수신 요청을 파싱해야 합니다.
  2. app.listen(PORT, () => {로 시작하는 마지막 섹션 바로 전에 기본 자바스크립트 파일에서 CRUD 코드에 연결할 수 있도록 src/index.js에 다음 코드 스니펫을 추가합니다.
const bodyParser = require('body-parser')
app.use(bodyParser.json())
app.use(
 bodyParser.urlencoded({
   extended: true,
 })
)
const db = require("../app/models");
db.sequelize.sync();
require("../app/routes/item.routes")(app);
  1. deployment.yaml 파일에서 배포를 수정하여 데이터베이스 연결 정보를 제공하는 환경 변수를 추가합니다.

다음 정의와 일치하도록 파일 끝에 있는 사양 항목을 업데이트하세요.

    spec:
      containers:
      - name: mynodejsapp
        image: mynodejsapp
        env:
        - name: DB_HOST
          value: ${DB_INSTANCE_IP}        
        - name: DB_PORT
          value: "5432"  
        - name: DB_USER
          valueFrom:
            secretKeyRef:
              name: gke-cloud-sql-secrets
              key: username
        - name: DB_PASS
          valueFrom:
            secretKeyRef:
              name: gke-cloud-sql-secrets
              key: password
        - name: DB_NAME
          valueFrom:
            secretKeyRef:
              name: gke-cloud-sql-secrets
              key: database
  1. DB_HOST 값을 데이터베이스의 주소로 바꿉니다.
export DB_INSTANCE_IP=$(gcloud sql instances describe mytest-instance \
    --format=json | jq \
    --raw-output ".ipAddresses[].ipAddress")

envsubst < deployment.yaml > deployment.new && mv deployment.new deployment.yaml

애플리케이션 배포 및 검증

  1. Cloud Shell 편집기 하단의 창에서 Cloud Code를 선택한 후 화면 상단의 Debug on Kubernetes를 선택합니다.
  2. 빌드와 테스트가 완료되면 출력 탭에 Resource deployment/mynodejsapp status completed successfully이 표시되고 URL이 나열됩니다. 'Forwarded URL from service mynodejsapp: http://localhost:8080'
  3. 항목을 몇 개 추가합니다.

cloudshell 터미널에서 아래 명령어를 실행합니다.

URL=localhost:8080
curl -X POST $URL/items -d '{"itemName":"Body Spray", "itemPrice":3.2}' -H "Content-Type: application/json"
curl -X POST $URL/items -d '{"itemName":"Nail Cutter", "itemPrice":2.5}' -H "Content-Type: application/json"
  1. 브라우저에서 $URL/items를 실행하여 GET을 테스트합니다. 명령줄에서 curl을 실행할 수도 있습니다.
curl -X GET $URL/items
  1. 삭제 테스트: 이제 실행하여 항목을 삭제해 봅니다. 필요한 경우 item-id의 값을 변경합니다.
curl -X DELETE $URL/items/1
    This throws an error message
{"message":"Could not delete Item with id=[object Object]"}

문제 파악 및 해결

  1. 디버그 모드에서 애플리케이션을 다시 시작하여 문제를 찾습니다. 다음은 몇 가지 팁입니다.
  • DELETE에 문제가 있다는 것을 알고 있습니다. 원하는 결과를 반환하지 않기 때문입니다. 따라서 itemcontroller.js->exports.delete 메서드에서 중단점을 설정합니다.
  • 단계별로 실행하고 각 단계에서 변수를 관찰하여 왼쪽 창의 로컬 변수 값을 관찰합니다.
  • request.params와 같은 특정 값을 관찰하려면 이 변수를 Watch 창에 추가합니다.
  1. id에 할당된 값은 undefined입니다. 코드를 변경하여 문제를 해결합니다.

수정된 코드 스니펫은 다음과 같습니다.

// Delete a Item with the specified id in the request
exports.delete = (req, res) => {
    const id = req.params.id;
  1. 애플리케이션이 다시 시작되면 삭제를 시도하여 다시 테스트합니다.
  2. 디버그 툴바의 빨간색 정사각형 647213126d7a4c7b.png을 클릭하여 디버깅 세션을 중지합니다.

6. 삭제

축하합니다. 이 실습에서는 새로운 Nodejs 애플리케이션을 처음부터 만들고 컨테이너가 있는 핫 배포 모드에서 작동하도록 구성했습니다. 그런 다음 기존 애플리케이션 스택에 있는 것과 동일한 개발자 흐름에 따라 애플리케이션을 원격 GKE 클러스터에 배포하고 디버깅했습니다.

실습을 완료한 후 정리하는 방법은 다음과 같습니다.

  1. 실습에서 사용된 파일 삭제
cd ~ && rm -rf mynodejsapp && rm -f setup.sh
  1. 프로젝트를 삭제하여 모든 관련 인프라 및 리소스를 삭제하세요.