# 使用 CircleCI 部署 Node.jS App 到 GCP App Engine (二) # GCP 我們將使用功能十分強大的 Google App Engine(GAE)進行部署,GAE 在處理大流量(load)時,例如訂票系統或是物流系統時非常適合。不但不用自己管理 load balance 的問題。Google 還會幫你自動開關 instances,使用者付費原則,用多少就付多少。 因為 GAE 是 Google Cloud Platform(GCP)下的一個服務所以若還沒有 GCP 先註冊註起來!新註冊的人可以享有一年 300 美金的試用,注意註冊時需要輸入一張信用卡才能開始使用,在試用階段 Google 並不會為向你收費,除非你主動跟他說要訂閱方案才會開啟收費流程。 註冊完後我們就能夠開始使用 GCP 了,進到 Google App Engine(GAE) 畫面,接下來點擊 建立應用程式。 ![](https://i.imgur.com/HobeZCM.png) # 部屬 # add app.yaml ```yaml runtime: nodejs # Name of the App Engine language runtime used by this application env: flex # Select the flexible environment manual_scaling: # Enable manual scaling for a service instances: 1 # 1 instance assigned to the service resources: # Control the computing resources cpu: 1 memory_gb: 0.5 disk_size_gb: 10 skip_files: # Specifies which files in the application directory are not to be uploaded to App Engine # We just need build/server.js to deploy - node_modules/ - .gitignore - .git/ - .circleci/ - src/ - test/ - .eslintrc.js - .huskyrc.js - .eslintignore - .prettierrc - babel.config.js - lint-staged.config.js - nodemon.json ``` >gcloud app deploy > ![](https://i.imgur.com/KkvBZ5M.png) # 沒有指定 在 package.json nodejs 版本 ![](https://i.imgur.com/3BlsfIl.png) # 沒有在 package.json指定 start 指令 >... Step #0: Application detection failed: Error: node.js checker: Neither "start" in the "scripts" section of "package.json" nor the "server.js" file were found. > # add package.json ```json= .... "scripts": { "lint": "eslint . --fix", "dev": "nodemon", "test": "jest", "build": "rm -rf build && babel src -d build --copy-files", "start": "node src\\server.js" } ``` ![](https://i.imgur.com/rFH10Eq.png) >rm -rf node_modules package-lock.json && npm install && npm start >yarn install --ignore-engines > # bug 太多 我們升級為 babel # package.json ```jsonld= { "name": "demo-server", "version": "1.0.0", "main": "index.js", "license": "MIT", "dependencies": { "express": "^4.17.1", "format": "^0.2.2" }, "devDependencies": { "@babel/cli": "^7.10.4", "@babel/core": "^7.10.4", "@babel/node": "^7.10.4", "@babel/preset-env": "^7.10.4", "babel-eslint": "^10.1.0", "babel-jest": "^26.1.0", "eslint": "^7.4.0", "eslint-config-airbnb-base": "^14.2.0", "eslint-config-prettier": "^6.11.0", "eslint-plugin-import": "^2.22.0", "eslint-plugin-jest": "^23.17.1", "eslint-plugin-prettier": "^3.1.4", "jest": "^26.1.0", "nodemon": "^2.0.4", "prettier": "^2.0.5", "supertest": "^4.0.2" }, "scripts": { "lint": "eslint . --fix", "dev": "nodemon", "test": "jest ", "build": "rm -rf build && babel src -d build --copy-files", "start": "babel-node src/server.js" } } ``` # src/server.js ```javascript= import express from 'express'; const app = express(); const { PORT = 3000 } = process.env; const IS_TEST = !!module.parent; // If there's another file imports server.js, then module.parent will become true app.get('/', (req, res) => { res.status(200).send('Hello!CI/CD!2'); }); if (!IS_TEST) { app.listen(PORT, () => { console.log('Server is running on PORT:', PORT); }); } export default app; ``` # test/server.test.js ```javascript= import supertest from 'supertest'; import app from '../src/server'; const PORT = 3001; let listener; let request; beforeAll(() => { listener = app.listen(PORT); request = supertest(listener); }); afterAll(async () => { await listener.close(); }); test('Server Health Check', async () => { const res = await request.get('/'); expect(res.status).toEqual(200); expect(res.text).toBe('Hello!CI/CD!2'); }); test('Server Health Check2', async () => { const res = await request.get('/'); expect(res.status).toEqual(200); expect(res.text).toBe('Hello!CI/CD!2'); }); ``` # 根目錄新增babel.config.js ``` module.exports = { presets: [ [ '@babel/preset-env', { targets: { node: 'current', }, }, ], ], }; ``` # run 測試 ok ![](https://i.imgur.com/hmYzxp7.png) # 部屬 # add package.json 都沒說這個 找有夠久 ```jsonld= ..... "start": "node build/server.js" ``` ![](https://i.imgur.com/SA2ZErB.png) 等到往生 ![](https://i.imgur.com/JJQAjze.png) ![](https://i.imgur.com/Qgef2vB.png) # 整合CircleCI deploy # add config.yml ``` version: 2.1 jobs: build: docker: - image: circleci/node:latest steps: - checkout - run: name: Check Node.js version command: node -v - run: name: Install yarn command: 'curl -o- -L https://yarnpkg.com/install.sh | bash' - restore_cache: name: Restore dependencies from cache key: dependency-cache-{{ checksum "yarn.lock" }} - run: name: Install dependencies if needed command: | if [ ! -d node_modules ]; then yarn install --frozen-lockfile fi - save_cache: name: Cache dependencies key: dependency-cache-{{ checksum "yarn.lock" }} paths: - ./node_modules - run: name: Lint command: yarn eslint . --quiet - run: name: Test command: yarn jest --ci --maxWorkers=2 - run: name: Build command: npm run build - persist_to_workspace: root: . paths: - build - package.json - yarn.lock - app.yaml deploy: docker: - image: google/cloud-sdk steps: - attach_workspace: at: . - run: name: ls command: ls -al - run: name: Setup gcloud env command: | echo $GCP_KEY > gcloud-service-key.json gcloud auth activate-service-account --key-file=gcloud-service-key.json gcloud --quiet config set project ${GCP_PROJECT_ID} gcloud --quiet config set compute/zone ${GCP_REGION} - run: name: Deploy to App Engine command: gcloud app deploy workflows: version: 2 build-deploy: jobs: - build - deploy: requires: - build filters: branches: only: master ``` ![](https://i.imgur.com/d5q6gXZ.png) # 填入 環境變數 進入申請金鑰 [ Cloud IAM ](https://cloud.google.com/iam/?hl=zh-tw) ![](https://i.imgur.com/2PX5sGI.png) 記得 GCP_KEY 是下載下來的 json 檔案 >GCP_KEY xxxxm" } GCP_PROJECT_ID xxxx1716 GCP_REGION xxxxt1-b > # 啟用App Engine Admin API ![](https://i.imgur.com/SLRntEQ.png) ![](https://i.imgur.com/C41hxky.png) # commit 部屬一次 ![](https://i.imgur.com/qvEITbn.png) 可以看到有再跑了 ![](https://i.imgur.com/7OvqLF4.png) 等10分左右 ![](https://i.imgur.com/E9Io8qx.png) ![](https://i.imgur.com/WttwJv4.png) 總算好囉 CD