跳到主要內容

發表文章

目前顯示的是 2020的文章

什麼是 CI/CD ? What is CI/CD

  Continuous Integration (簡稱 CI) 開發人員頻繁地交付最新的更動到原始碼儲存庫, 並觸發自動化的建置和測試, 來確保最新版本的軟體可以正常地被運行 而 CD 又有兩種定義 Continuous Delivery, Continuous Deployment Continuous Delivery (簡稱 CD) 在這個階段會將 CI 過程中產生的檔案 (稱為 Artifact 產物, 如程式的執行檔, 設定檔案, 容器映像) 保存起來 ( 如上傳到 FTP, Source Repository, Image Registry) 然後將產品發布到不同的環境, 如 QA 或是 UAT 環境, 進行功能測試 (Function Testing), 整合測試 (Integration Testing), 以及E2E 測試等等 Continuous Deployment (也簡稱 CD) CI/CD的最後一個階段, 將 production-ready 的軟體部署到 Production 的環境讓新功能上線  

使用 GCS 快速部署 React 網站 | Host React static website via Google Cloud Storage

前言 這年頭要部署一個網站再也不用像十年前這麼地麻煩了, 這也驗證了科技始終來自於人性, 複雜的操作流程最終還是會被簡化成簡單的步驟 現在使用 Google Cloud Storage (簡稱 GCS) 的服務即可以用來快速地部署網頁 Step 1. 建立網頁 以下使用 React 建立的網頁來進行示範 $ npx create-react-app my-app $ cd my-app $ npm run build Step 2. 在 GCS 上建立 storage bucket  在 GCS 上建立一個放置網頁的空間 # gsutil mb gs://[BUCKET_NAME]/ $ gsutil mb gs://mywebapp777/ Step 3. 上傳檔案 將網頁上傳到 mywebapp777 的 bucket 內  # gsutil cp [OBJECT_LOCATION] gs://[DESTINATION_BUCKET_NAME]/ $ gsutil cp -r * gs://mywebapp777/ Step 4. 公開 預設是私有的, 若想讓他人可以存取, 必須將 bucket 內的檔案都公開 # gsutil iam ch allUsers:objectViewer gs:/[BUCKET_NAME] $ gsutil iam ch allUsers:objectViewer gs:/mywebappp777 Step 5. 設定 index page 設定首頁 # gsutil web set -m [INDEX_PAGE] -e [ERROR_PAGE] gs://[BUCKET_NAME] $ gsutil web set -m index.html -e 404.html gs://mywebappp777 Step 6 設定 Load Balancer 建立一個新的 Load Balancer 選擇 Http(s) Load Balancing 選擇 From Internet to my VMs 建立一個 Load Balancer 首先, 替 Load Balancer 命名,  接下來在 Bac...

GCP Cloud Build 跨專案部署 | Use Cloud Build to deploy service to different project

前言 開發者可以將測試以及部署的腳本定義在 cloudbuild.yaml 的設定檔中, 讓 Cloud Build 照著腳本去執行指令 # Build the module. steps: - name: 'gcr.io/cloud-builders/gcloud' args: [ 'run', 'deploy', 'myapp', '--set-env-vars', 'SERVICE_ENV=stage', '--image', 'gcr.io/build-prj/myapp', '--platform', 'managed', '--region', 'us-central1' ] 但預設 Cloud Build 的服務帳號只擁有當下專案的權限, 若想要做到跨專案部署應用程式的話, 就需要授予 Cloud Build 的服務帳號在另一個專案下的部署權限 本篇文章將會簡單的介紹如何跨專案部署服務 假設我們有兩個 Project, 一個是 Build Project 用來放 Source Code 以及跑 Cloud Build, 另一個是 App Project 用來部署服務 (如下示意圖) 要實作這個機制, 首先 授予部署權限 需要授予在 Build Project (8750547431)下的 Cloud Build 的服務帳號能在 App Project (2498738606)下部署的相關權限 (如下) Service Account User Cloud Run Admin APP_PRJ=app-prj BUILD_PRJ_NUM=8750547431 # Cloud Run Admin: can deploy Cloud Run Service gcloud projects add-iam-policy-binding $APP_PRJ \ --member serviceAccount...

如何使用 Cloud Build 來達到自動化測試與佈署| Create a CI/CD Pipeline On Cloud Build

前言 GCP 的 Cloud Build 是個非常好用的 DevOps 工具, 對於使用 Cloud Source Repository 的開發者來說,  不需要繁複的設定,  就能快速的建立 CI/CD Pipeline, 使推送到 Cloud Source Repository 的交付能自動觸發 Cloud Build 的事件執行測試與部署  除此之外無伺服器的平台, 不需要額外管理 CI/CD 的伺服器, 也不避擔心 Scale 的問題,  這也是許多開發者選擇 Cloud Build 的原因之一 以下就簡單的帶各位了解如何在 Cloud Build 上建立 Pipeline 跑 CI/CD 的流程 首先, 建立 Pipeline 腳本 由於 Cloud Build 是基於容器的服務, 腳本上的指令都必須跑在容器的環境內, 所以在建立腳本的時候, 需要特別地指定執行環境的容器映像( 即 Builder 的映像), 如下 - name: 'gcr.io/cloud-builders/docker'   args: ['build', '-t', 'gcr.io/myproject/myapp', '.'] 目前 GCP 上已經內建了幾個常用的容器映像讓開發者使用, 所以想要實作 CI/CD 的 Pipeline 並不一定需要自己建立 Builder 的映像檔 預設所有要被執行的腳本都需要被定義在 cloudbuild.yaml 的檔案裡 內容如下 steps: - name: 'gcr.io/cloud-builders/docker' args: ['build', '-t', 'gcr.io/myproject/myapp', '.'] - name: 'gcr.io/cloud-builders/docker'   args: ['push', 'gcr.io/myproject/myapp'] - name: 'gcr.io/cloud-buil...

淺談 Docker Network, None, Bridge, Host

前言 Docker 目前提供以下網路模式, None, Bridge, Host None 除了  loopback interface (127.0.0.1) 之外無其他網路介面, 所以無法存取外部網路 andy_lai@cloudshell:~$ docker run -it --network=none ubuntu:14.04 /bin/bash Digest: sha256:ffc76f71dd8be8c9e222d420dc96901a07b61616689a44c7b3ef6a10b7213de4 root@71aa5f112c7e:/# ifconfig lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 UP LOOPBACK RUNNING MTU:65536 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) Host 使用和宿主機 (Host) 一樣的網路設定, 效能會比較好, 但仍不建議使用, 需要考慮網路安全性的問題, 除此之外也會使失去容器隔離的好處  Bridge Docker 預設會在宿主機 (Host) 上建立一個 docker0 的網路介面, 容器可以透過這網路介面跟外界溝通, 也可以透過這介面作容器間的溝通, 事實上 docker0 扮演著一個虛擬的網路橋接器,  當以 Bridge 的網路模式建立新的容器的時候 andy_lai@cloudshell:~$ docker run -it -p 8080:8080 my-docker docker0 會被接上虛擬網卡 veth3cc413e and...

如何在 Google App Engine 上除錯, 使用 Cloud Trace, Log Viewer

前言 Google App Engine (GAE) 提供了非常方便的環境讓開發者可以輕鬆地部署服務, 除此之外還有許多有用的功能族繁不及備載... 但若如果部署在 GAE 上的服務發生異常時, 我們該如何去追蹤問題呢? 以下簡單介紹一下幾個 GCP 上好用的服務幫助我們可以快速的縮小問題的範圍 Cloud Trace Cloud Logging Cloud Trace 這個服務可以讓我們快速的找到效能上的瓶頸是來源自哪隻 API 可以看出問題可能出在 /v1/interest/ads_pref 這隻API 上, 並且發生時間可能在  17:40 分左右 Cloud Logging 知道效能的瓶頸是源自於某隻 API 之後, 接下來可以用 Log Viewer 來看程式是否有拋出任何異常的訊息 在 Query Builder 下查詢語法來找出可能的線索 resource.type="gae_app" resource.labels.module_id="My-Service" textPayload:"exec bulk" timestamp >= "2020-05-15T17:40:00+08:00" AND timestamp <= "2020-05-15T17:43:00+08:00" 這個語法會找出 My-Service 這隻服務在 2020-05-15 這天17:40~17:43 產生含有 exec bulk 這段訊息的 log  以下是其他查詢可以用的運算子 =           # equal !=          # not equal > < >= <=   # numeric ordering :           # "has" matches any substring in the log entry field =~    ...

錯誤訊息 does not have storage.objects.get access to the Google Cloud Storage object

當我們想要建立 Cloud Run 的容器映像時, 可以使用 gcloud builds submit gcloud builds submit --tag gcr.io/andy-prj/my-service --project $PROJECT 若執行的過程中若出現以下的錯誤訊息時 Creating temporary tarball archive of 124 file(s) totalling 570.2 KiB before compression. Uploading tarball of [.] to [gs://andy-prj_cloudbuild/source/3422.582116-0fd81c021c243d892201dfd08ce1550.tgz] Created [https://cloudbuild.googleapis.com/v1/projects/andy-prj/builds/46712e03-3a15-403f-8be1-b1f1a238d02e]. Logs are available at [https://console.cloud.google.com/cloud-build/builds/ 46712e03-3a15-403f-8be1-b1f1a238d02e ?project=182871]. ERROR: (gcloud.builds.submit) HTTPError 403: <?xml version='1.0' encoding='UTF-8'?><Error><Code> AccessDenied </Code><Message> Access denied .</Message><Details>deploy-cloud@andy-prj.iam.gserviceaccount.com does not have storage.objects.get access to the Google Cloud Storage object .</Details></Error> 表示需要我們缺少存取 GCS 裡物件的權限 要解決這個問題...

錯誤訊息 Permission iam.serviceaccounts.actAs denied

當我們想要在 Cloud Run 部署新的服務的時候, 可以使用 gcloud run deploy  gcloud run deploy bi-cronjob \ --image gcr.io/$PROJECT/bi-cronjob \ --platform managed \ --no-allow-unauthenticated \ --region us-central1 \ --project $PROJECT \ --set-env-vars SERVICE_ENV=$RUNMODE \ --memory 1G 若部署的過程中出現以下的錯誤訊息時 Deploying container to Cloud Run service [my-service] in project [andy-prj] region [us-central1] X Deploying...   . Creating Revision...   . Routing traffic... Deployment failed ERROR: (gcloud.run.deploy) PERMISSION_DENIED: Permission   'iam.serviceaccounts.actAs' denied on service account  compute@developer.gserviceaccount.com (or it may not exist). 表示需要 Service Account User 的權限才能完成部署的動作 要解決這個問題, 可以到 IAM 的頁面下加入此權限 或是使用 gcloud 的指令來提昇權限 gcloud projects add-iam-policy-binding <PROJECT_ID> \ --member=serviceAccount:<SERVICE_ACCOUNT_ID>@<PROJECT_ID>.iam.gserviceaccount.com \ --role=roles/iam.serviceAccountUser

修改資料表的欄位資訊 Change Column In BigQuery

前言 與傳統的關聯式資料庫不同, BigQuery 並不支援使用 ALTER 來修改資料表內的欄位, 如果很不幸的真的需要改欄位, 我們只能重新建立新的資料表 CREATE OR REPLACE TABLE ` dataset.usr_new ` AS SELECT * EXCEPT (usrType), CAST(usrType AS INT64) AS usrType, FROM `dataset.usr`; 分區表 (Partitioned Table) 以上的方法只適合用在普通的資料表, 若想要修改分區表內的欄位資訊, 建議使用 bq query 的指令來做 bq query 支援將查詢的結果寫入到指定的資料表中, 除此之外還可以用查詢結果來建立分區表(如下) bq --location=location query \ --destination_table project_id:dataset.table \ --time_partitioning_field column \ --use_legacy_sql=false \ 'query' 分區表都會有兩個虛擬的欄位 _PARTITIONDATA, _PARTITIONTIME 用來記錄資料的時間,  這邊需要注意的是 bq query 沒辦法將舊資料表內的虛擬欄位的資訊直接寫到新的資料表中,  若想將這些資訊保留下來, 必須額外建立新的欄位來放這些資訊 SELECT * EXCEPT (usrType), CAST(usrType AS INT64) AS usrType, _PARTITIONTIME AS createdAt FROM ` dataset.usr `; 完整的指令 bq query \ --destination_table 'dataset.usr_new' \ --time_partitioning_field createdAt \ --use_legacy_sql=false --append_table \ ' SELECT * EXCEPT (usrType), CAST(usrType AS INT64) ...

ElasticSearch rename field name in index

前言 傳統的關連式資料庫 (Relational Database, RDB), 若想要變更資料表內的欄位名稱可以用一行指令簡單地做到 MS SQL sp_rename 'table_name.old_column_name', 'new_column_name', 'COLUMN'; PostgreSQL ALTER TABLE Test1 RENAME COLUMN foo TO baz; 但如果想要在ElasticSearch上修改欄位的話就沒有這麼簡單了 假設現在有個 Index "user" PUT /user HTTP/1.1 Content-Type: application/json { "settings": { "number_of_shards": 1 }, "mappings": { "_doc": { "properties": { "devOs": { "type": "keyword" }, "devOsVer": { "type": "keyword" }, "appVer": { "type": "keyword" } } } } 如果想要將欄位 appVer 改成 ver, 基本上需要以下的步驟 Step 1. 用更新後的 Mapping 建立一個暫時的 Index, user_tmp PUT /User_tmp HTTP/1.1 Content-Type: application/json { "settings": { "number_of_shar...

Git取消合并 Undo a merge by pull request

前言 實務上在多人開發的情況下, 很常在上 Code 的時候被 Git 要求先做 PULL 但若是 PULL 下來的內容有問題,  特別是當同仁交付了一段會造成系統崩潰的內容至版本庫 (Repository) 時, PULL 下來的 Commit 就會使正在開發的功能受到影響, 若無法在短時間內修正, 嚴重的話甚至會影響到整個軟體專案的開發, 造成其他新功能都無法進行部屬 使用 Revert git pull 做的事情就是 Fetch + merge, 所以事實上可以使用 git revert 把剛剛的 Merge Commit 打掉 找出要打掉的 Merge Commit Id 執行 Revert 指令 $ git revert -m 1 結果如下 使用 Reset $ git reset --hard <MERGE 之前的 COMMIT_ID> 不建議用這個方法, 因為執行完上面的指令之後, 雖然 HEAD 會往前移動, 工作目錄會被還原到合併之前的 Commit, 但是在重新上 Code 時 Git 還是會要求做 PULL, 因為遠端分支的 HEAD 還是指在比較新的 Commit 上(有問題的  Commit) 除非強行覆蓋遠端版本庫中的內容, 如下 $ git push --force 但實務上並不是每個開發團隊都接受使用--force 的指令作強制覆蓋, 所以要取消 Merge Commit, 建議還是使用 Revert 指令

Group By Then Limit And Offset in Elastic Search

前言 傳統的 Relational database 可以使用 SQL 語言以 GROUP BY 的方式將資料分群然後做計算 SELECT country, COUNT(*) AS Count FROM `bi_dataset.usr` GROUP BY country DESC ORDER BY 1 若想要在 Elastic Search  上實現相同的結果的話, 可以使用 Terms Aggregation { "size": 0, "aggs" : { "country" : { "terms" : { "field" : "country" } } } } 但實務上基於效能的考量, 通常不會希望回傳全部的結果, 而是以分頁的方式傳回部分資料 SQL的做法是在語法裡加入 LIMIT 以及 OFFSET 來控制結果 SELECT country, COUNT(*) AS count FROM `dataset.usr` GROUP BY country ORDER BY 1 DESC LIMIT 20 /* 限制資料量 */ OFFSET 100 /* 取得某特定區段的資料, 即 skip 前100筆 */ 而在 Elastic Search 裡, 可以藉由 bucket sort aggregation 來實現, 分頁的部分可以透過 size, 以及from 來控制 { "size": 0, "aggs": { "country": { "aggs": { "sort_country": { "bucket_sort": { "sort": [ { "_count": { ...

[解決方法] Cloud Scheduler Permission Denied

前言 Cloud Scheduler 有個進階的功能, 可以設定當排程被觸發的時候, 必須先取得指定的 IAM 身份, 才能去打指定的 Cloud Run 或是 Cloud Function 上的服務 整體的流程大致如下圖 Cloud Scheduler 發出 POST 請求取得 Token Cloud IAM 回傳 Token Cloud Scheduler 帶著 Token 打去打 Cloud Run 上的服務 最後 Cloud Run 根據 Token 是否擁有 Cloud Run Invoker 的權限來決定是否要執行這個請求 %3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%3E%3CmxCell%20id%3D%221%22%20parent%3D%220%22%2F%3E%3CmxCell%20id%3D%222%22%20value%3D%222%22%20style%3D%22ellipse%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3Baspect%3Dfixed%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%22206%22%20y%3D%22197.5%22%20width%3D%2219%22%20height%3D%2219%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3C%2Froot%3E%3C%2FmxGraphModel%3E 若部署完後在 Log 頁面出現 PERMISSION_DENIED 的話該如何處理??? {   "jobName": "projects/cloud-833005/locations/us-central1/jobs/sync_usr",   "targetType": "HTTP",   "url": "https://ukrjq-uc.a.run.app/api/job/load",   ...

Golang 的物件導向: Golang Object Oriented Programming

前言 Go 不是一個典型的物件導向程式語言 (Object Oriented Programming, OOP), 所謂的 OOP 三大精神 "封裝", "繼承",  與 "多形" 在Go語言中其實並沒有被完整的實現,  取而代之的是以結構來代替類別, 以組合來代替繼承, 以及介面實作來達到多形 結構與類別的差異 Go 允許將相關的資料組織在同一個結構 (struct) 裡, 甚至還可以為其定義方法, 這個作法跟其他 OOP  語言 (如 C# ) 將資料封裝在類別裡的概念很像 type User struct { Hight int Weight int Name string } func (p * User) GetBMI() int { return p.Weight / p. Hight * p. Hight } 差別是結構沒有建構子可以用, 所以通常是要自己寫一個 New_ _ _( ) 的函式來實現相關的初始化的動作 func NewUser(h, w int, name string) *User{ p:= &Page{Hight: h, Weight: w} if name == "" { p.Name = "Unknown" // set default value } return p } 組合 Composition Go 沒有繼承, 若想要重用程式碼必須以 has-a 的方式將其他結構的資料嵌入 (Type Embedding) type BusinessUser struct { User // <------------------ type embedding CreditCarNumber string } func NewBusinessUser(h, w int, name, cred string) *User{ if cred == "" { return nil } p:= &Bus...

Calculate DAU, WAU, and MAU by BigQuery

前言 近年來, 商業智慧( Business Intelligence, BI )的運用已漸漸地受到企業的重視, 決策者可以藉由 BI 提供的資料來做業務的分析與預測 在做業務分析的時候, 通常會以每日活躍使用者數( Daily Active User, DAU ), 每週活躍使用者數( Weekly Active User, WAU ), 以及每月活躍使用者數( Monthly Active User, MAU )的多寡, 來暸解目前產品的營運狀況 要計算出這些資訊其實並不難, 首先以 Group By "日期"的方式算就可以先算出 DAU WITH DAUtbl AS ( SELECT _PARTITIONDATE AS date, COUNT(*) as dau FROM my_dataset.usr WHERE _PARTITIONDATE BETWEEN DATE_SUB(DATE('2020-03-16T13:15:30Z'), INTERVAL 7 - 1 DAY) AND DATE('2020-03-21T13:15:30Z') GROUP BY _PARTITIONDATE ) 有了每日人數( DAU )之後, 接下來就是來算 WAU 計算 WAU 時, 須將當日以及前六天的 Active User 數量都加總起來 而想要取得七天內的資料, 可以先透過 CROSS JOIN 的方式將每一日的資料都複製成七份 SELECT * FROM DAUtbl, UNNEST(GENERATE_ARRAY(0, 7 - 1 )) i 每個副本都會有個索引值 i (如下) 有了七份的副本之後, 再將資料移動到合適的群組(日期)做加總, 最後就可以獲得每一日 WAU 的數目 SELECT DATE_ADD(date, INTERVAL i DAY) date_grp <--- 移動資料 , SUM(DISTINCT IF( i < 7 ,dau, null)) wau FROM DAUtbl, UNNEST(GENERATE_ARRAY(0, 7 - 1 )) i GROUP BY 1 ORDER BY...