[實作筆記] Gitlab Runner

前情提要

我想完成什麼 ?
如何用最佳解完成全端應用的開發 ?

  1. 前端應包含 Web、App(iOS、Android 或簡稱三螢) 與其它
  2. 後端不限語言、框架(.Net Core、nodejs)
  3. 測試部署應該自動化(CI/CD)
  4. 成本應優化
  5. 應該使用雲原生的技術與 know how

這次我們專注在第 3 點的 CI/CD 上,為了第 1 點,我建置的專案為 flutter
選用的 CI/CD Server 為 Gitlab , 我將會在這裡作較多的著墨,
最終的產出物是 flutter app image, 並推送到 image registry 上 。

概觀,每個多邊型都應該可以被置換

如上圖,每個多邊型都應該可以被置換,

  • Application 可以被換成 Nodejs/.Net Core/ …
  • Gitlab Runner 有三種,本文中會使用 specific runners ,另外有 2 種
    • Shared runners(需要信用卡認証身分,每月限制 400 分鐘)
    • Group runners (在 Gitlab 上可以)
      我將 Gitlab 設定在本機 ( MacBook Pro ) 環境上,下文會講解細節,
      也可以置換到雲端的伺服器上,搭配 K8S
      ex:
      • GKE: Google Cloud Platform Kubernetes Engine
      • EKS: Amazon Web Services’ Elastic Kubernetes Services
      • AKS: Microsoft Azure Kubernetes Service
  • Container Registry 這裡我使用最主流的 Docker Hub Registry
    以雲原生的三大平台都有對應的功能,如果有機會應該優先選用。

工具準備

首先需要 Gitlab Account ,我們選用 Gitlab 作為 CI/CD Server,
這個階段我們只會作到持續整合,而未部署,相當於只有 CI 的部份。
理論上也可以選用其它的 CI/CD 服務,像是 Azure 或是 GitHub , 這裡就不作過多的展開。

Docker , 簡單說我們的工作只有兩個步驟

  1. 在 Gitlab 你的專案 > Settings > CI/CD > Runners 註冊 Runner Executors
    • 一般來說,我們會選用 docker 或 Kubernetes,本章我會用 docker 為例
  2. 撰寫 Pipeline 與 Job 腳本,
    Pipeline
    我們預計執行以下的工作(Pipeline)
    • Test
      • Run Test
      • Run Lint
    • Build
      • Build Application
      • Build Image

開始

註冊 Gitlab Runner

我們要在 docker 建立來執行 gitlab Runner

  1. Create the Docker volume:

    1
    docker volume create gitlab-runner-config
  2. Start the GitLab Runner container using the volume we just created:

    1
    2
    3
    4
    docker run -d --name gitlab-runner --restart always \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -v gitlab-runner-config:/etc/gitlab-runner \
    gitlab/gitlab-runner:latest
  3. Register Docker Runner

    依序輸入 Gitlab URL、gitlab-ci 的 token、runner 說明/名稱、Runner 的 tag、executor、docker image

    1
    2
    docker run --rm -it -v gitlab-runner-config:/etc/gitlab-runner \
    gitlab/gitlab-runner:latest register
    • Enter your GitLab instance URL (also known as the gitlab-ci coordinator URL).
    • Enter the token you obtained to register the runner.
    • Enter a description for the runner. You can change this value later in the GitLab user interface.
    • Enter the tags associated with the runner, separated by commas. You can change this value later in the GitLab user interface.
    • Provide the runner executor. For most use cases, enter docker.
    • If you entered docker as your executor, you’ll be asked for the default image to be used for projects that do not define one in .gitlab-ci.yml.
      GitLab instance URL 是 https://gitlab.com/
      你可以在專案中的 Settings > CI/CD 找到 token,
      description 會顯示在 Runner List 中,可以用易懂的描述,
      tags 可以更多的參考這本篇文章設定
      executor 選用 docker 記得需要安裝 docker daemon
      executor 為 docker 時,需要註明預設的 image,我是選用 docker:stable

執行 RUNNER

gitlab-runner run

移除 RUNNER(為了重新安裝)

  1. 驗証 runner 狀態是否 alive

    gitlab-runner verify –delete

  2. 移除 gitlab-runner

    gitlab-runner unregister –all-runners

參考

錯誤: 當使用 MacBook M1 當作 RUNNER

在 RUNNER JOB 中執行 flutter pub get 時
$ flutter pub get
發生錯誤訊息如下,當我換了一個 intel 晶片的 MacBook 當作 RUNNER 就不會有問題了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Running "flutter pub get" in rettulf...
===== CRASH =====
si_signo=Trace/breakpoint trap(5), si_code=128, si_addr=(nil)
version=2.14.4 (stable) (Wed Oct 13 11:11:32 2021 +0200) on "linux_x64"
pid=57, thread=116, isolate_group=main(0x4002e9fc00), isolate=main(0x4002e7d000)
isolate_instructions=4001cf2ec0, vm_instructions=4001cf2ec0
pc 0x0000ffffab7e28ec fp 0x000000400a1af838 Unknown symbol
pc 0x0000ffffab49ed0f fp 0x000000400a1af898 Unknown symbol
pc 0x0000ffffab78114d fp 0x000000400a1af8d8 Unknown symbol
pc 0x0000ffffacaa294c fp 0x000000400a1af940 Unknown symbol
pc 0x0000ffffacaa220b fp 0x000000400a1af980 Unknown symbol
pc 0x0000ffffacd028ff fp 0x000000400a1af9f8 Unknown symbol
pc 0x0000004001e6a123 fp 0x000000400a1afaa0 dart::DartEntry::InvokeCode(dart::Code const&, unsigned long, dart::Array const&, dart::Array const&, dart::Thread*)+0x153
pc 0x0000004001e69f75 fp 0x000000400a1afb00 dart::DartEntry::InvokeFunction(dart::Function const&, dart::Array const&, dart::Array const&, unsigned long)+0x165
pc 0x0000004001e6c55d fp 0x000000400a1afb50 dart::DartLibraryCalls::HandleMessage(dart::Object const&, dart::Instance const&)+0x15d
pc 0x0000004001e93e46 fp 0x000000400a1afc30 dart::IsolateMessageHandler::HandleMessage(std::__2::unique_ptr<dart::Message, std::__2::default_delete<dart::Message> >)+0x596
pc 0x0000004001ebe7ac fp 0x000000400a1afca0 dart::MessageHandler::HandleMessages(dart::MonitorLocker*, bool, bool)+0x14c
pc 0x0000004001ebeecf fp 0x000000400a1afd00 dart::MessageHandler::TaskCallback()+0x1df
pc 0x0000004001fdde48 fp 0x000000400a1afd80 dart::ThreadPool::WorkerLoop(dart::ThreadPool::Worker*)+0x148
pc 0x0000004001fde27c fp 0x000000400a1afdb0 dart::ThreadPool::Worker::Main(unsigned long)+0x5c
pc 0x0000004001f57a48 fp 0x000000400a1afe70 /sdks/flutter/bin/cache/dart-sdk/bin/dart+0x1f57a48
-- End of DumpStackTrace
qemu: uncaught target signal 6 (Aborted) - core dumped
/sdks/flutter/bin/internal/shared.sh: line 225: 57 Aborted "$DART" --disable-dart-dev --packages="$FLUTTER_TOOLS_DIR/.packages" $FLUTTER_TOOL_ARGS "$SNAPSHOT_PATH" "$@"
Cleaning up project directory and file based variables

DIND(Docker In Docker)

我們的 Gitlab-Runner 是執行在 Docker 上,
而當我需要 build Docker Images 時,我會在 Container 中建立 docker
這時 Container 所使用的 Image 會是 Docker Image
就被稱為 DIND (Docker In Docker)

在 Gitlab CI 中 Docker Login

參考以下的 .gitlab-ci.yml

1
2
before_script:
- echo "$DOCKER_REGISTRY_PASS" | docker login -u $DOCKER_REGISTRY_USER --password-stdin

異常紀錄與解決方案

$ echo -n $LOGIN_KEY | docker login -u _json_key_base64 –password-stdin https://xxxx-docker.pkg.dev
Error: Cannot perform an interactive login from a non TTY device

主因是讀不到 $LOGIN_KEY 的環境變數,使用 Gitlab-CI 的話要注意 Protect Variable

無法對 docker hub registry

在 RUNNER JOB 中執行 docker push
錯誤訊息如下:

dial tcp: lookup docker on x.x.x.x:53: no such host error runner inside docker on armhf

這裡要修改 RUNNER 中的設定檔 config.toml , 路徑參考如下:

You can find the config.toml file in:

  • /etc/gitlab-runner/ on *nix systems when GitLab Runner is executed as root (this is also the path for service configuration)
  • ~/.gitlab-runner/ on*nix systems when GitLab Runner is executed as non-root
  • ./ on other systems

在 [[runners]] > [runner.docker] 加入 image = docker:stable 或是 privileged = true
just add image = docker:stable  and privileged = true
這可能不是一個正確的 solution , 可以更多的參考這篇討論

在 DIND 編輯文件

如上題,我們需要在 container 之中編輯文件,
這裡我會使用 vim,
不過安裝前記得先更新 apt-get
不然會出現以下錯誤

E: Unable to locate package vim on Debian jessie simplified Docker container

1
2
3
4
apt-get update
apt-get install apt-file
apt-file update
apt-get install vim # now finally this will work !!!

GCP Artifact Registry

這裡都以台灣的 region 為範例,
所以都是 ‘asia-east1-docker.pkg.dev‘ 不同的 region 請再參考 GCP 文件

推上去

記得要先登入

docker login -u _json_key_base64 –password-stdin https://asia-east1-docker.pkg.dev

推上去

docker push “asia-east1-docker.pkg.dev/{env_name}/docker/{project_name}:{version_tag}

拉下來

需要先調整 Docker configuration file 取得授權,請參考

gcloud auth configure-docker asia-east1-docker.pkg.dev

再執行

docker pull asia-east1-docker.pkg.dev/{env_name}/docker/{project_name}:{version_tag}

註冊

問題

  1. gitlab-ci-multi-runner 與 gitlab-runner 的差異為何 ?
  2. 當 docker gitlab-runner image 的 instance 執行 gitlab-runner run 會產生以下訊息, 這代表什麼意思 ?
    Configuration loaded builds=0
    listen_address not defined, metrics & debug endpoints disabled builds=0
    [session_server].listen_address not defined, session endpoints disabled builds=0

(fin)

[實作筆記] 在 terminal 中控制字串

目的

在 CI/CD 的過程需要透過 shell 處理環境變數產生版號,
以前沒有這方面的經驗,實務上也不像一般的應用程式語言那麼常操作,
避免未來忘記,特別記錄下來,如果未來有相關的語法也會在此同步更新。

宣告變數

直接給值

var=””

切割字串

只想看結果的話

echo ${HOME}|cut -d”/“ -f2
指定給變數
var=$(echo ${HOME}|cut -d”/“ -f2)

串接字串

var=$(echo ${HOME}|cut -d”/“ -f2).$USER

(fin)

[踩雷筆記] MacBook M1 silicon X Docker X MySQL Image

前情提要

新工作配發了新的 MacBook 搭載最新的 M1 晶片,
沒想到反而遇到了許多問題, 不會有太多的文字說明,
僅用來記錄我實作有效的解決方法

情境:在 Docker 跑 MySQL

當執行以下語法時,

1
docker pull mysql 

會得到錯誤訊息如下

no matching manifest for linux/arm64/v8 in the manifest list entries

解決方案

拉取映像檔

docker pull mysql/mysql-server:latest

建立 mysql container

docker run -d –name mysql -p 3306:3306 mysql/mysql-server:latest

取得原始 root 密碼

docker logs mysql 2>&1 | grep GENERATED

[Entrypoint] GENERATED ROOT PASSWORD: mysql_password_shows_here

連線至 container 的 image 執行 mysql 指令

docker exec -it mysql bash

使用原始 root 密碼登入 mysql

mysql -u root -p
Enter password:

輸入原始的 root 密碼後,
會看到畫面如下:

Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 25
Server version: 8.0.27

Copyright (c) 2000, 2021, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective owners.

Type ‘help;’ or ‘\h’ for help. Type ‘\c’ to clear the current input statement.

重設密碼

ALTER USER USER() IDENTIFIED BY ‘password’;

授權

CREATE USER ‘root‘@’%’ IDENTIFIED BY ‘root’;
GRANT ALL ON . TO ‘root‘@’%’;
flush privileges;

修改 root 密碼

ALTER USER ‘root‘@’%’ IDENTIFIED WITH mysql_native_password BY ‘password’;
flush privileges;

(fin)

[翻譯] react-router 的三種渲染方法(component、render、children)

前情提要

本文內容大量參考此系列文章, 僅作記錄之使用。
在學習 React 的過程中, 我們需要處理瀏覽器的網址(URL)與頁面之間的關係,
目前(2021 年)主流的作法,就是使用 react-router 這個 library,
其中有三個類似的方法

Rendering Method

component

使用方法:

1
2
3
4
5
<Route path="/" component={Home} />
//Same as
<Route path="/" >
<Home />
</Route>

這個方法的缺點是並沒有提供傳遞 props 的 API

1
2
3
4
const Home = (props) => {
console.log(props);
return <div>Home</div>;
};

render

Render 這方法要求你使用一個傳入一個回傳 component 的方法,
我們可以透過方法參數傳遞 props

1
2
3
4
5
6
<Route
path="/contact"
render={(routeProps) => {
return <Contact name={name} address={address} {...routeProps} />;
}}
/>

children

基本上使用方式與 render 並無二致,
最大的差異在染渲邏輯,children 在路由不匹配的時候, 仍然會顯示,
以下例子在使用者輸入 / 會顯示 PortfolioContact

1
2
3
4
<Route path="/" exact component={Home} />
<Route path="/about" render={() => <About></About>} />
<Route path="/portfolio" children={() => <Portfolio></Portfolio>} />
<Route path="/contact" children={() => <Contact></Contact>} />

參考

(fin)

[實作筆記] 前端置多語系 i18next

前情提要

多語系是國際化的專案很重要的一部份,
這次有機會接觸到國際化的前端的案子,
記錄一下前端主流的 i18next 如何設定。

設定

  1. 安裝 i18n

    1
    2
    3
    4
    5
    # npm
    $ npm install i18next --save

    # yarn
    $ yarn add i18next
  2. 準備多語系 JSON 檔, 為 Key-Value 形式, Value 為 string
    允許巢狀, 範例如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    {
    "zh": {
    "Week": {
    "Monday": "周一",
    "Tuesday": "周二",
    "Wednesday": "周三",
    "Thursday": "周四",
    "Friday": "周五",
    "Saturday": "周六",
    "Sunday": "周日"
    }
    },
    "es": {
    "Week": {
    "Monday": "Monday",
    "Tuesday": "Tuesday",
    "Wednesday": "Wednesday",
    "Thursday": "Thursday",
    "Friday": "Friday",
    "Saturday": "Saturday",
    "Sunday": "Sunday"
    }
    }
    }
  3. 起始設定

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import i18next from "i18next";
    import resources from "./resources"; // 載入上一步的 JSON 檔

    i18next.init({
    lng: "zh", // 預設語言
    debug: true,
    resources: resources,
    });

    // 使用方式
    document.getElementById("output").innerHTML = i18next.t("Week.Sunday");
  4. 追加 React 設定

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import React from "react";
    import i18n from "i18next";
    import { useTranslation, initReactI18next } from "react-i18next";
    import resources from "./resources"; // 載入上一步的 JSON 檔

    i18n
    .use(initReactI18next) // passes i18n down to react-i18next
    .init({
    resources: resources,
    lng: "en", // if you're using a language detector, do not define the lng option
    fallbackLng: "en",
    interpolation: {
    escapeValue: false, // react already safes from xss => https://www.i18next.com/translation-function/interpolation#unescape
    },
    });

    在 React 中使用

    1
    2
    3
    4
    5
    6
    7
    8
    import ReactDOM from "react-dom";
    function App() {
    const { t } = useTranslation();
    return <h2>{t("Week.Sunday")}</h2>;
    }

    // append app to dom
    ReactDOM.render(<App />, document.getElementById("root"));

其它 Framework 請參考

參考

(fin)

[生活筆記] 拋棄 LastPass 擁抱 BitWarden

前情提要

2021 年 3 月, 看到了一則新聞, 指出 LastPass 將改變它們的經營策略,
免費版本將只支援一種裝置, 行動裝置或電腦。
產品收費是天經地義, 但消費者找尋更高性價比的產品也是。
讓我想離開 LastPass 的主因在於, 突襲式的宣布讓我幾乎沒有反應時間,
免費版的限制太過苛刻, 一樣在限制裝置的功能上,
EverNote 這款筆記軟體也是逐步降低免費版本可跨平台的數量。
但是 LastPass 更狠, 瞬間降到僅限一種, 這基本上是在玩文字遊戲,
因為大多數的人應該都同時具備手機與個人電腦, 一般上族班只能被迫選擇電腦,
或是付費了事。付費當然不是問題, 但宣告期極短且粗爆的方式讓人覺得被當韭菜割。

當我稍微作了一下功課, 發現這類的服務已經多如繁星, 甚至內建在作業系統或是瀏覽器之中。
再進一步比較功能, 與自已所需要的功能(密碼保管、密碼產生器、跨裝置etc…)
有更多免費的軟體已經能滿足我的需求,比如說: BitWarden

比較心得

BitWarden 的好處,

  1. 免費帳戶即可跨裝置存取密碼
  2. 可以匯入 LastPass 匯出的密碼
  3. 有密碼產生器
  4. 有瀏覽器外掛,支援自動填入
  5. 開源方案

最後一點我是覺得最吸引我的地方,這表示如果你有一定的程式語言能力,
你將可以自行搭建一個密碼管理的伺服器,這點甚至對企業也是有相當的吸引力,
而 BitWarden 使用的語言為 TypeScript、C# 與 Python。

在轉換上是非常容易的,可以在一小時內完成這項作業
實務操作上也沒有遇到太大的困難,除了原本 LastPass 右鍵產生密碼的方式,在 BitWarden 上面沒有外,
其它功能或是外觀設計上,我都覺得 BitWarden 的表現更加優秀,
甚至有點相見恨晚的感覺。

至於沒有的功能,不是什麼大事,而且開源專案誰都可替他加上這個功能。
但對我來說,我已經忘了 LastPass 有什麼優點,
轉移到了 BitWarden 6 個月,從未再次開啟,於是今天正式將帳號刪除了。

參考

(fin)

[實作筆記] Azure Functions 存取 Key Vault

前情提要

新的工作有機會接觸到微軟的 Azure Cloud 服務,
包含 AWS 、 GCP 的話,世界的三大雲服務都碰過一輪了,
不過也只是皮毛而已, 紀錄下來免得遺忘 .

說明一下需求,在實作授權的

在這裡我會使用到 Azure Cloud 的三個服務,

  1. Azure Functions
  2. Managed Identity
  3. Key Vault

使用的服務簡介 TL;DR

說明一下我理解的三個服務:

Azure Functions 是微軟無伺服器(Serverless),
Serverless 的概念是讓開發人員專注開發, 減少對伺服器維運的成本,
並且提供極度彈性的可擴展性,並且可以使用更少的資源(更低的成本),
最大的限制是運算時間(timeout),
但就我個人而言的 Best Practice 在 300 秒內完成不了的運算, 應考量整體架構的瓶頸在哪裡,
而不是採取無限制的付費方案(Plan Type)。
稍微作個 ORK 評量,應該在 0.2 | 10 | 300 秒內完成計算。

Managed Identity 是微軟用來作身份認証與授權的服務,
與其它雲服務最大的差別應該是它是基於 Azure Active Directory (Azure AD) 之上,
一般來說會建議使用遵循(Role-based Access Control),
下面的圖片很好的說明了其概念。

如何使用適用於 Azure 資源的受控識別?

Key Vault 是個相對簡單的概念, 在開發中我們會接觸到需多的密鑰、証書、連線字串等等資訊…
這些資訊有敏感性, 但對開發過程又不是最重要的東西,
這些資訊在單體架構(Monolithic)時常常面臨開發與資安的兩難,
而 Key Vault 可以解決這個問題(當然不同的 Cloud 也有類似的解決方案)

案例說明

這次的案例,使用者會在瀏覽器提供給我們一組 Token ,
而我們會拿這組 Token 與第三方互動換回我們需要的 Key,
最後我們會把這組 Key 存進 Key Vault 給其它服務使用.
流程簡介

你可能會好奇, 在上圖中 Managed Identity 扮演的角色為何 ?
就是在 Function Apps 存取 Key Vault 這一段,
我們將提供一組 Identity 給 Function Apps 讓它有權限存取 Key Vault .

實作

我將步驟大致分為以下幾步

  1. 建立 Function App 與 function
  2. 啟用 Function App 的 Managed Identity
  3. 建立 Key Vault
  4. 設定 Key Vault Access policies
  5. 設定 Function App Configuration
  6. 修改 Function

讓我們開始吧.

建立 Function Apps 與 function

開始前先作名詞解釋, 因為微軟的命名會讓剛接觸的人十分混淆

  • Azure Functions : 微軟的 Serverless 產品名稱, 我們可以在這個服務下建立許多 Function Apps 的實體
  • Functions Apps : 主要運作的實體, 也是我們要設定的地方,每個 App 裡面可以有多個 function
  • Functions : 主要邏輯所在的地方, 以開發的角度來說, 我們應該只關心這塊

建立 Function Apps 的步驟較為簡單,請依照微軟文件即可,
接下來選擇 Function Apps > 選擇剛剛建立的 Functions App > Create > Http Trigger C#,
選擇這個範本我們將以 C# 語言實作,授權等級我選擇 Anonymous .

建立好後,可以在 Code + Test 裡面查看預設的程式.
這裡建立的是 C# 指令碼, 開發上的細節請參考官方文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using System.Net;

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
log.Info("C# HTTP trigger function processed a request.");

// parse query parameter
string name = req.GetQueryNameValuePairs()
.FirstOrDefault(q => string.Compare(q.Key, "name", true) == 0)
.Value;

if (name == null)
{
// Get request body
dynamic data = await req.Content.ReadAsAsync<object>();
name = data?.name;
}

return name == null
? req.CreateResponse(HttpStatusCode.BadRequest, "Please pass a name on the query string or in the request body")
: req.CreateResponse(HttpStatusCode.OK, "Hello " + name);
}

啟用 Function App 的 Managed Identity

移動到 Functions Apps 的 Settings > Identity,
可以看 System assigned > Status 將它切換為 On.
這一步的設定是為了讓服務之間可以受到 AAD 控管而不需要你自行處理.

建立 Key Vault

在建立之前也是需要一些名詞解釋,

  • Key Vault: 微軟的服務名稱
  • key vault section: Key Vault 的實體區塊(可以想像成一個實體檔案,裡面可以存很多 Key, 並且我們可以對這個檔案進一步設定,也可以參考 官方的文件快速建立)
  • Keys : 在 key vault section 的 settings 中有三種類型的資料,Keys、Secrets 與 Certificates,

找到 Key Vault > key vault section > Settings > Secrets , 建立一組 Secret

建立 Key Vault

點選剛剛建立的 Secret 並且選擇 CURRENT VERSION 取得 Secret Identifier(複製下來, 待會會用到)

設定 Key Vault Access policies

一樣在 Key Vault > Settings > Access policies > Add access policy ,
在 Select principal 找到剛剛建立的 Function App,
在 Configure from template (optional) 我們直接套用 Secret Management,
這允許我們的 Function App 會取得管理 Key Vault Secret 的權限.

設定 Function App Configuration

移動到 Function App > Settings > Configuration > Application settings
我們要在這裡新加一個 Application Setting

Name 自已取但是請記得,我們稍後就會用到,這裡我先命名為 TestKV,
Value 請參加下面的範例, 將前面步驟取得的 Secret Identifier 填到 SecretUri 之後.

1
@Microsoft.KeyVault(SecretUri=https://mykv.vault.azure.net/secrets/RefreshToken/xxxx)

這裡有小朋友問我, 那為什麼不把資料設定在 Function App Configuration 就好,
這樣子開發人員也碰不到資料, 要修改也是很有彈性的.
我的回答是, 這樣子其它的服務也需要這個資料怎麼辦呢?
Copy-Paste 會造成維護上很大的困難, 我們會希望維持一組就好.
放在 Key Vault 可以讓我們不同的服務共用.
Overview

反思 Best Practice 應該怎麼作???
設定檔散落在不同的層級, 還是應該集中管理, 再用 Config 橋接???

修改 Function 的代碼

下列我們用 TestKV 這組設定與 C# 作為範例 :
透過環境變數取得 Key Vault 的資料

1
2
3
// Get
var testKV =
Environment.GetEnvironmentVariable("TestKV", EnvironmentVariableTarget.Process);

而如果你需要寫入請使用 Azure SDK Client Library

1
2
3
4
5
6
7
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
/// Skip ...
/// Set
var kvUri = $"https://{KeyVaultName}.vault.azure.net";
var client = new SecretClient(new Uri(kvUri), new DefaultAzureCredential());
client.SetSecret($"{SecretKey}", $"{SecretValue}");

其它語言可以參考

參考

(fin)

[實作筆記] 自我簽署憑証, localhost run with https

前情提要

最近有一批學生在學習 Express 開發網站,
他們需要透過 facebook 應用程式作登入。
遇到了以下的問題
Facebook 偵測到 所使用的網連線並不安全

最簡單的解決方式,重新選擇你的 Facebook Application Type 為無

如下:
!Facebook Application Type 選擇無
這樣足以解決學生們在練習開發上的問題了。

不過我有點好奇背後的真正原因是什麼 ?

Facebook 的政策

Facebook 的文件說明

使用 HTTPS

使用具有加密功能的 HTTPS 作為網際網路通訊協定,而非 HTTP。HTTPS 會維護傳送資料的隱私,保護其不受竊聽攻擊。
此外,也能保護資料在傳送過程中不遭到置入廣告或惡意程式碼的竄改。

在 2018 年 10 月 6 日,所有應用程式都必須使用 HTTPS。

可能是一個原因,因為學生們的開發環境建置出來都是單純的 http ,同學也有作出相關的猜測。
那麼我們要怎麼快速驗証這個想法呢 ?

https-localhost

這是一個最快速的驗証方法,不過需要修改部份的代碼,而且遮蓋了需多細節。
以下以 MacOS 操作為主

首先,需要安裝相依的 NSS Tool

brew install nss

安裝 https-localhost

npm i https-localhost

改寫在 Express 網站的 app.js

1
2
//const app = express()
const app = require("https-localhost")();

基本這樣就完成了。

  • 優點
    • 改動步驟少
    • 無需理解細節
  • 缺點
    • 要改動原始代碼

自我簽署憑証

接下來我們會深入一點細節,並了解背後的機制。

憑証機制簡介

我不打算從 TCP、Http 講解到 Https,網路上已有相當多的資訊,
或是未來有機會再補充。
總而言之,當你使用 Https 時,有一個很重要的觀念是,
你的 Server 應該提供一組公鑰給 User 作為資料的加解密。
那延伸的問題就是怎麼確認這組公鑰是可以信任的呢?

簡單說就是透過第三方的公正單位進行認証,所謂憑証認証機構(CA)。
我們相信這些機構,而機構也會幫我們進行審核。
而一般來說你的應用程式,比如說瀏覽器會先安裝好可信任的根憑証。

一個經典的說明是這樣的:

鮑伯可以隨便把憑證向外發布。
鮑伯與愛麗絲事先可能互不認識,但鮑伯與愛麗絲都信任伊凡,
愛麗絲使用認證機構伊凡的公鑰驗證數位簽章,如果驗證成功,便可以信任鮑勃的公鑰是真正屬於鮑伯的。[1]
愛麗絲可以使用憑證上的鮑勃的公鑰加密明文,得到密文並傳送給鮑伯。
鮑伯可以可以用自己的私鑰把密文解密,得到明文。

以開發者而言,你可以自我簽署憑証,
這裡你要同時扮演鮑伯,愛麗絲與伊凡,
所以常有人會在其中迷失方向,角色錯亂。

伊凡(local CA)

伊凡是一個 CA,我們只要安裝 mkcert
就可以建自一個本地端的 CA

brew install mkcert

我們來對 localhost 發行憑証

mkcert localhost

會產生下列兩個檔案,
分別為憑証 localhost.pem
與私錀 localhost-key.pem

鮑伯(Server)

這個時候我們角色切換至鮑伯(Server)
鮑伯在這個過程中最重要的發行公錀,
私錀是用來加密傳輸的資料的。

這裡需要修改一下 Server 的程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const fs = require("fs");
const https = require("https");

/// 中略…

https
.createServer(
{
key: fs.readFileSync("localhost-key.pem"),
cert: fs.readFileSync("localhost.pem"),
},
app
)
.listen(3000, function () {
console.log(
"Example app listening on port 3000! Go to https://localhost:3000/"
);
});

這樣我們就可以允許愛麗絲透過 Https 與我們溝通囉。

愛麗絲(Client)

基本你只要開啟瀏覽器,輸入 https://localhost:3000/ 你就可以正常瀏覽了。
但我們要了解背後發生的過程,

安裝 mkcert 完畢後,其實會在你的機器(與瀏覽器)上安裝根憑証,
這樣愛麗絲才會信任伊凡
可以透過以下語法查詢安裝路徑(不同 OS 會有差異)

mkcert -CAROOT

這個作法改動的程式也不會比較少,
但至少不再有黑魔法(當然你也可以去追蹤 https-localhost 的代碼)

Recap

快速回顧一下 mkcert 作了哪些事

  1. 建立一個 local CA
  2. 替你的瀏覽器安裝 CA
  3. 對 localhost 發行憑証

而你的程式需要

  • 改使用 https 啟動網站

別忘了解決學生問題

回到 facebook 應用程式登入的問題,
別忘了去設定 callback 網址,並且確定是走 https
用戶端 OAuth 設定 > 有效的 OAuth 重新導向 URI
不然會得到以下錯誤

忘了設定 callback URL

小計

文末的聯結有需多是透過 OpenSSL 的作法供參考,
但是會多一步驟需要安裝手動憑証(愛麗絲該作的事)。

另外仍然需要修改程式這點我不是很滿意,
應該可以掛上其它的 Proxy Server,由 Proxy Server 處理 https 就好,
內網單純走 http。
最近也有看到零信任網路
不過那應該是另一個議題了

Zero Trust 零信任架構近年來日受重視,在 Zero Trust 框架底下,
即便使用者在內網環境,也不會給予沒有期效或過高之存取權限。

參考

(fin)

[活動筆記] 利用單元測試來開發『猜數字』小遊戲

前情提要

一直有在收集一些 Kata 用來練習 TDD ,
剛好台中的敏捷社群有這樣的活動, 第一時間就報名參加,
也很幸運的搶到票, 下面是我的記錄與一些心得反饋。
不過我沒有 Php 的經驗, 或許會有一些偏誤。

希望有機會可以整理出一系列的 Kata ,
讓人可以在學習 Coding 過程中將 Testing 變成一種首選(反射行為、自然反應 etc …)
可以參考我之前的 Blog 從 TDD 到 TDD, Todo 到 Test 趨動開發

活動流程

  • 主持人簡單介紹, 並邀請大家輪流發言, 說明參加的目的

    這個引導技巧蠻好的, 有 Check-In 與 Warm-up 的效果
    雖然是還是有人不發言或是後續引發的討論有點少(直接用肯定句結束對話)
    但是相信多試幾次是可以改善的

  • 主講者介紹題目
  • 主講者實作題目
    • 拆分「猜數字」與「隨機數」
    • 使用 Injection 的技巧
    • 解題
  • 分組解「隨機數」
  • 主講者講解隨機數
  • 問問題

心得

看到各地開始有不同的講者來說明 Unit Test 或 TDD 感覺是很棒的,
希望能夠有更多的社群與講者來介紹. 這個工程師的基本技能

在講解的過程中, 可能是我誤會了, 但是我感覺到講者不確定是不是能講更多,
畢竟測試、單元測試(Unit Test)、測試趨動開發(TDD)的重心都有一些不同,
也許在報名前作一次篩選, 或是同時準備基本進階的教材,
Check-In 的階段, 或隨著課程進行由淺入深的介紹,
就以猜數字來說 Injection 也許可以放入進階的部份再介紹,
反而有位與會者在 Check-In 提出的問題我覺得蠻好的。

如何對猜數字這個題目, 進行需求分析, 可惜沒有進一步的研討。
第二部份的猜數字, 包含了隨機的部份, 分組討論我沒有參與到其它組別,
不太確定討論的效果為何? 但是在我的組別是有人完全不說話的,
總之線上活動與會者的參予是非常重要的,
另外在實作猜數字的測試案例時, 講者使用了我們這一組的代碼,
講者似乎有不同的看法, 它將 3 個案例合併成了 1 個,
但是在移除案例時, 並沒有實測可以保護原有案例的部份.
這裡可以處理的更細緻一些.

最後如果能有完整流程的 commits 與錄影的話更好,
當然主辦與講者可能有不同的考量就不予置評了.

用 C# 重新跑一次 Kata

我打算用 C# 重新進行一次這個 Kata 並稍作記錄

規則說明

猜數字是一個經典的遊戲, 分為出題者與答題者,
出題者必需隨機設定一組四碼不重複的數字, 答題者嘗試去猜出正確的數字,
如果答題者猜對數字且位置正確, 出題者必需給一個 A,
如果答題者猜對數字但位置不正確, 出題者必需給一個 B,
如果 4A0B 就代表答對, 遊戲結束.

設計

出題的部份, 我們可以開發給出題者輸入, 或是隨機產生題目,
隨機出題的部份我們可以拉出來當另一個功能, 所以我們先專注在猜數字的邏輯上
先是 A 的邏輯, 表示位置與數字相同, 所以我會需要定位與數字比較
B 的邏輯, 表示位置與數字相同, 這裡我會發現不存在數字比較的邏輯,
而是字串或字符的比對.
大概就這樣, 很簡單來設計我們的 Case 吧

猜數字案例

前設條件:答案為1234

  1. 5678 應該得到 0A0B : 目的是產生方法的介面與參數
  2. 1678 應該得到 1A0B : 最簡單的方式可以檢查第 1 個數字是否符合
  3. 1278 應該得到 2A0B : 最簡單的方式可以檢查第 2 個數字是否符合, 產生重複
  4. 重構 2、3 案例, 消重複使用 ACounter 作為記錄
  5. 1238 應該得到 3A0B : 目的是確定上面的重構有效
  6. 1283 應該得到 2A1B : 最簡單的方式檢查是否符合包含 3
  7. 1243 應該得到 2A2B : 最簡單的方式檢查是否符合包含 4, 產生重複
  8. 重構 6、7 案例, 消重複使用 BCounter 作為記錄
  9. 2143 應該得到 0A4B : 確認上面的重構有效

出題者的設計

這裡我覺得不適合只使用單元測試, 應該搭配整合測試可以有更好的效果

設計隨機出題

  1. 每次都是隨機的
  2. 是 4 個數字
  3. 4 個數字不重複

隨機答案案例

  1. 呼叫應得到數字 : 定義介面, 無傳入值, 回傳字串, 但是每個字元都是數字
  2. 呼叫應得到長度為 4 的字串 : 限制回傳長度
  3. 重構:使用正規表示式合併兩個 Case
  4. 4 個數字不能重複

後記

  1. 將 SetAnswer 由 public 轉為 private,內化為由程式隨機出題(也就是沒有出題者,實務上這層級的改動應參考需求)
  2. 第二層抽象, 將 SetAnswer 實作包裝一層介面, 抽象為取出不重複數字,
    但是將字串職責留在 SetAnswer 之中
  3. 實作介面, 測試轉移到這個實作之中, 猜數字的測試改用相依性注入作 Mock
  4. C# 語言特性並沒有對集合的洗牌擴充方法, 抽出這一部份單獨實作, 並使用整合測試進行測試
  5. 改寫隨機的洗牌方法以符合 Fisher–Yates 的演算法

參考

(fin)

[實作筆記] terminal 設定 alias

前情提要

在換了新電腦後, 越來越常使用 terminal,
在學習了 Vim 之後, 常常不開任何的 IDE 直接在 terminal 的編輯檔案,
指令的使用頻率也更高了, 所以試著設定一下 alias 來提昇開發的效率.
什麼是 alias ? 它可以幫助我們使用較短指令, 達到與完整指令一樣的效果,
當我們常用的指令縮短了, 就可以加開我們開發的速度.

設定檔案

需要修改 .bashrc 或是 .zshrc 檔, 因為我使用 iTerm 所以要在 .zshrc 裡設定.
目前我最常使用的為 git,此外 hexo (一種靜態網站的發佈策略, 我用來設定我的 blog),
設定如下:

git

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
alias gac="git add . && git commit -m" # + commit message
alias gi="git init && gac 'Initial commit'"
alias gp="git push" # + remote & branch names
alias gl="git pull" # + remote & branch names
alias gs="git status"
## Pushing/pulling to origin remote
alias gpo="git push origin" # + branch name
alias glo="git pull origin" # + branch name
## Pushing/pulling to origin remote, master branch
alias gpom="git push origin master"
alias glom="git pull origin master"
## Branches
alias gb="git branch" # + branch name
alias gc="git checkout" # + branch name
alias gcb="git checkout -b" # + branch name

Hexo

1
2
3
alias hxg="hexo g"
alias hxs="hexo s"
alias hxd="hexo d"

參考

-Git aliases for lazy developers

(fin)

Please enable JavaScript to view the LikeCoin. :P