這邊的與世隔絕當然不是真的與世隔絕啦! 我指的是無法連上外面的docker registry的環境, 例如docker hub或是quay.io, 開發環境指的是你要在local可以用來開發測試的standalone模式

首先, 你要先裝好docker(或podman)

如果有private registry

有幾個images會需要放到registry裡面的, 像是

  • dapr
  • 3rdparty/redis
  • 3rdparty/zipkin
  • placement
  • daprd

列表可以在 這邊 找到 (這邊的是放在github上的)

接著安裝dapr cli, 如果可以直接連上internet, 那就用官方文件的做法:

wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | DAPR_INSTALL_DIR="$HOME/dapr" /bin/bash

但如果不行呢? 那就想辦法到github上下載cli回來安裝

因為要從private history來安裝dapr, 所以在dapr init要多下一個參數, 像是:

dapr init --image-registry MY_REGISTRY_URL

這樣就會從你private registry抓回來裝了

但如果連private registry都沒辦法放上相關的image呢?

不依靠docker registry安裝

Dapr很貼心呀! 還有install bundle, 可以想辦法先去前一個連結裝bundle回來

這個bundle裡面已經有個dapr cli了, 所以解開後, 把dapr複製到你要的目錄, 例如:

sudo cp ./dapr /usr/local/bin

現在我們就有cli可以用了, 但images怎辦? bundle裡面其實就包含有相關的image的tar檔了, 所以把init方式改成:

dapr init --from-dir . -s

這樣就會用local image安裝了, 這邊多加了個-s表示是slim mode, 沒有redis, 沒zipkin的, 因為local images沒有放, 但如果自己需要(比如說要寫state store需要redis), 那就要自己去加component

以redis當state store為例: 假設我們需要一個redis來當state store, 且這redis我們預先跑在本機, port為為6379, 此store名稱為mystore, 我們可以在~/.dapr/components/這目錄加上一個mystore.yaml,

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: mystore
spec:
  type: state.redis
  version: v1
  metadata:
  - name: redisHost
    value: localhost:6379
  - name: redisPassword
    value: ""
  - name: actorStateStore
    value: "true"

這樣我們就有一個名叫mystore的state store了, 不只state store, 其他也可以如法炮製

那如果想用podman取代docker呢?

dapr init --from-dir . -s --container-runtime podman

剛好想說要用Varish來做一下Minio(S3)的cache, 研究了一下順便做個紀錄

先在Ubuntu上裝來試試, 可以用apt 來安裝:

apt install varnish

在Ubuntu 20.04上(WSL用的版本)是6.2.1的版本, 最新版應該是7.2, 不過沒差, 做法都一樣

Varnish預設的設定在/etc/varnish/default.vcl, 打開這檔案你就可以看到像這樣的內容:

vcl 4.0;

# Default backend definition. Set this to point to your content server.
backend default {
    .host = "127.0.0.1";
}
sub vcl_recv {
    # Happens before we check if we have this in cache already.
    #
    # Typically you clean up the request here, removing cookies you don't need,
    # rewriting the request, etc.
}

sub vcl_backend_response {
    # Happens after we have read the response headers from the backend.
    #
    # Here you clean the response headers, removing silly Set-Cookie headers
    # and other mistakes your backend does.
}

sub vcl_deliver {
    # Happens when we have all the pieces we need, and are about to send the
    # response to the client.
    #
    # You can do accounting or modifying the final object here.
}

Varnish的設定檔用的是一種叫做vcl的語言, 它會被Varnish先compile過後才會被使用, 所以改好這檔案後, 如果你跑 sudo system start varnish (這是WSL2上用的, 其他地方可能就是systemctl), 如果你寫錯了, 一開始跑就可以發現出錯了

以上面那個例子來說, 它會預設快取你local上的web server

但如果是要連接Minio (S3)是不夠的, 因為如果單純把backend設成 Minio server, 那client還是會需要access key和secret key才可以存取, 如果你希望讓它跟存取靜態網站一樣, 那你可以能會希望把這兩個設定放在後端

Vanish出場是沒支援可以call S3 API的, 這時候就要透過一個VMOD - AWSRest, 這VMOD是可以在你去backend (Minio/S3) 拿資料前先幫你用你的access key, secret key算好簽章(signature), 所以我們要先安裝這個VMOD

安裝VMOD你會先需要libvarnishapi-dev, 可以用apt install libvarnishapi-dev來安裝, 另外AWSRest還會需要mhash, 你還會需要安裝apt-get install libmhash-dev

裝好後, 從 https://github.com/xcir/libvmod-awsrest 抓取最新的source code, 進入目錄後執行

./autogen.sh
./configure
make
sudo make install

沒意外的話就可以完成安裝, 要確認是不是已經安裝好了, 我們可以在default.vcl加上

vcl 4.0;
import awsrest;

# ....

重啟varnish有成功, 表示應該是沒啥問題才對

我在我本地端電腦跑了個Minio, port為9000, 有一個bucket叫做mmmbux, 裡面有個檔案, key為20220101/a.c, access key為TGhYs2FYBGMYueAz, secrect key為IM2SgF7LxIlZVbeo3Vv7OdQzA7pnZFB1, Varnish則是跑在port 6081上

首先我們來看看怎讓client/browser在不用提供access key/secret key的狀況下可以存取物件

vcl 4.0;
import awsrest;

backend default {
    .host = "127.0.0.1";
    .port = "9000";
}

sub vcl_recv {
    set req.http.host = "127.0.0.1";
    awsrest.v4_generic(
        service           = "s3",
        region            = "ap-northeast-1",
        access_key        = "TGhYs2FYBGMYueAz",
        secret_key        = "IM2SgF7LxIlZVbeo3Vv7OdQzA7pnZFB1",
        signed_headers    = "host;",
        canonical_headers = "host:" + req.http.host + awsrest.lf()
    );
}

Ok, 其實就很簡單的在vcl_recv上加上那幾行就好, 這時候你就可以用 http://localhost:6081/mmmbux/20220101/a.c 來存取 mmmbux 這bucket上 2022/0101/a.c 這個檔案了

那, 如果我不想把bucket name當作url的一部分呢?

sub vcl_recv {
    set req.http.host = "127.0.0.1";
    set req.url = "mmmbux/" + req.url
    awsrest.v4_generic(
        service           = "s3",
        region            = "ap-northeast-1",
        access_key        = "TGhYs2FYBGMYueAz",
        secret_key        = "IM2SgF7LxIlZVbeo3Vv7OdQzA7pnZFB1",
        signed_headers    = "host;",
        canonical_headers = "host:" + req.http.host + awsrest.lf()
    );
}

上面這段就是把你進來的url加上mmmbux/當新的url, 這樣做的話, 你的新url就會是 http://localhost:6081/20220101/a.c

那如果我想進一步, 把它變成 http://localhost:6081/files/20220101/a.c 呢?

sub vcl_recv {
    set req.http.host = "127.0.0.1";
    
    if (req.url ~ "^/files/") {
        set req.url = regsub(req.url, "^/files/", "/mmmbux/");
        awsrest.v4_generic(
          service           = "s3",
          region            = "ap-northeast-1",
          access_key        = "TGhYs2FYBGMYueAz",
          secret_key        = "IM2SgF7LxIlZVbeo3Vv7OdQzA7pnZFB1",
          signed_headers    = "host;",
          canonical_headers = "host:" + req.http.host + awsrest.lf()
        );
    } else {
        return(synth(404));
    }
}

上面這段就是把/files/後面的都到 mmmbux這bucket去抓, 然後其他目錄都回傳 404 Not found

那如果我想要用docker跑也要有這VMod呢? 我把這Dockerfile的範例放在https://github.com/julianshen/varnish-awsrest-docker, Varnish 官方的docker image有提供 install-vmod 這script讓你安裝vmod, 所以只需要給它awsrest的tarball: https://github.com/xcir/libvmod-awsrest/archive/refs/tags/v70.12.tar.gz 即可, 我做了一個現成的image放在quay.io/jlnshen/varnish-awsrest

一般來說, container通常會設計成只專注在它單一的任務上, 也就是通常不會把一個http server跟db server跑在同一個container內, Kubernetes 的Pod的設計, 讓我們可以在同一個Pod內放多個containers, 因此可以延伸出init container, sidecar container來輔助原本的container, 中間可以透過分享Volume或是直接透過loopback網路來共享資料, 但還是會有情境是, 你會希望可以在某個container空間內執行某個程式, docker的話, 你可以用 docker exec ,那在Kubernetes呢?

Kubernetes 也是可以用kubectl 達成同樣的目的, 像是:

 kubectl exec mycontainer ls

其實跟docker exec 是類似的, 如果是要執行shell進去執行其他的維護:

kubectl exec mycontainer -it /bin/sh

但如果是, 你要從其他的pod去執行其他的pod裡面的指令呢? 像是用Cron job定期去執行某特定pod裡面的程式?

我們也是可以透過呼叫Kubernetes API來達成這目的的, 如同下面這個範例:

func runCommand(clientset *kubernetes.Clientset, pod string, ns string, cmd ...string) (string, error) {
	req := clientset.CoreV1().RESTClient().Post().Resource("pods").Name(pod).Namespace(ns).SubResource("exec")
	option := &v1.PodExecOptions{
		Command: cmd,
		Stdin:   false,
		Stdout:  true,
		Stderr:  true,
		TTY:     false,
	}

	var stdout bytes.Buffer
	var stderr bytes.Buffer
	req.VersionedParams(option, scheme.ParameterCodec)

	exec, err := remotecommand.NewSPDYExecutor(config, "POST", req.URL())
	if err != nil {
		return "", err
	}

	sopt := remotecommand.StreamOptions{
		Stdout: &stdout,
		Stderr: &stderr,
	}

	err = exec.Stream(sopt)

	if err != nil {
		return "", err
	}

	if stderr.String() != "" {
		return stdout.String(), errors.New(stderr.String())
	}

	return stdout.String(), nil
}

由於Kubernetes API還沒包裝exec這個資源, 所以要用.Resource("pods").Name(pod).Namespace(ns).SubResource("exec")去取用, stdin, stdout, stderr都還是可以串接回來的

但如果你直接在你的pod內執行這段的話, 其實不會成功的, 因為你沒有權限可以去做, 你必須先設定好一個Role如下:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: executor
rules:
  - verbs:
      - get
      - list
    apiGroups:
      - ''
    resources:
      - pods
      - pods/log
  - verbs:
      - create
    apiGroups:
      - ''
    resources:
      - pods/exec

我的情境是會用list pods找出pod的名稱, 在用這個名稱去找到特定的pod執行, 所以會需要前半段get和list的權限, 後半段對pods/execcreate的權限才是執行這段程式真正需要的

要達成這件事, 只需要利用turf.js就好, turf.js提供了一大堆處理地理資訊的相關工具, 只拿來算距離, 還真算有點小用 :P

以下是範例:

const { point } = require('@turf/helpers');
const distance = require('@turf/distance').default;

const from = point([120.9758828, 24.8043379]);
const to = point([120.92846560331556, 24.846169955749275]);

const result = distance(from, to, { units: 'kilometers' });

這邊point回傳的是GeoJson的Feature, 由於GeoJson是經度在前緯度在後, 如果你去Google map複製座標來的話, 會剛好相反, 這邊需要注意, 以上就可以算出這兩點到底距離幾公里了

turf.js可以做的相當多, 算距離只是其中之一, 如果你要找出像是包含所有點的最小矩形, 或是合併多個多邊形(像是合併行政區域), 都可拿來使用

WSL 當作開發環境固然方便好用的, 但也吃蠻大空間的, 最近在開發的東西, 需要跑一個postgresql, 裝不少資料, 吃掉我蠻多硬碟空間的, 偏偏我SSD就只有小小的512G (好吧, 的確寒酸到不像開發者的電腦), 一下子就吃滿滿了, 所以就必須要把這個給搬到我另一個比較大的磁碟救急(說是救急的意思是, 預期它會吃上1TB, 不過這又是會碰到一個問題, 之後再解了)

WSL新開的映像檔都是放在C:(畢竟不是謎片,不會自動住到D槽…咦?!), 要把它搬家的話, 需要先把它export出來, export的方法很簡單:

wsl --shutdown
wsl --export Ubuntu-20.04 d:\ubuntuback.tar

先shutdown是想保險一點, 所以也先把相關的視窗(像是Terminal, VSCode)都關一關, 如果映像檔很大, 這預期要做非常久, 像我這個有200G以上, 放下去, 基本上我就去看電視不管它了, 當然也要確保一下你目標硬碟夠大, 這邊Ubuntu-20.04是我要備份的目標, 如果不知道名字是啥可以用wsl -l查詢

export完之後, 接著就用:

wsl --import Ubuntu20dev e:\wsl\dev D:\ubuntuback.tar

這邊Ubuntu20dev是新的名字, 不要跟舊的重複了, import完後就可以用wsl -d Ubuntu20dev 登入進去玩了

不過登入後, 咦, 等一下, 怎麼會是用root? 之前舊的並不是呀! 要解決這個問題, 新增這個檔案/etc/wsl.conf, 裡面內容是

[user]
default=yourloginname

把wsl再shutdown之後再重新進來就不會是root了