Object **self 是 C 語言的 pointer to pointer,常用在想在 callee 操作 caller 的資料。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct Object {
int a, b;
}

typedef struct Object Obejct;

int init_object(Object **self) { // call-by-value
if (NULL == (*self = malloc(sizeof(Object))))
return -1;
(*self)->a = 0;
(*self)->b = 0;
return 0;
}

int main(int argc, char *argv[]) {
Object *o = NULL;
init_object(&o);
o->a = 9922;
o->b = 5566;
return 0;
}

C Pointer to pointer

C 語言是 call by value, self 的 value 是 &o 的 value,也就是 o 的 address。在 init_object()self dereference、用 *self 操作等同用 o 操作,所以 *self = malloc(sizeof(Object)) 等同讓 o 指向新 allocate 出來的 memory,而 (*self)->a 等同 o->ao 是個 Object 的 pointer, o->a 是它指向的 Object 的 field a(*self)->a = 0 是在初始化 main()o pointer 指向的 Object

如果不使用 pointer to pointer,像下面這樣會發生什麼事?

1
2
3
4
5
6
7
8
9
10
11
int init_object(Object *self) {
if (NULL == (self = malloc(sizeof(Object))))
return -1;
self->a = 0;
self->b = 0;
}

int main(int argc, char *argv[]) {
Object *o = NULL;
init_object(o);
}

這時候 self 的 value 是 o 的 value,也就是 NULL。在 init_object()self 是個 local variable,allocate 一塊 Object 大小的 memory 給它以及其他操作都不會如我們希望的影響到 main() 裡的 o

Array types are always one-dimensional but may be composed to form multi-dimensional types.

from spec Array types

A slice is a descriptor for a contiguous segment of an underlying array and provides access to a numbered sequence of elements from that array.

Like arrays, slices are always one-dimensional but may be composed to construct higher-dimensional objects.

from spec Slice types

Initialization

Slice

在多維的情況下,inner slice 只能一個個初始化,像這樣:

1
2
3
4
x := make([][]byte, 2)
for i := range x {
x[i] = make([]byte, 1000)
}

上面例子沒有迴圈直接去 access x[0][0] 的話會 index out of range

或者用 literal 直接寫:

1
2
3
4
a := [][]uint8{
{0, 1, 2, 3},
{4, 5, 6, 7},
}

Array

初始化成零值比較簡單:

1
2
c := [5][5]uint8{}
// [[0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0]]

Memory Size

多維 array 的 size 是 type size * element 數量

多維 slice 的 size 則要看多維 slice 是怎麼組成的。slice 由指向 underlying array 的 pointer、length、capacity 組成,多維 slice 不同的組合方式會造成有不同數量的 slice header 而有不同的 size,來個例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
// x 是 2 x 1000 的 slice
x := make([][]byte, 2)
for i := range x {
x[i] = make([]byte, 1000)
}
fmt.Println(len(x), len(x)*len(x[0]))

// y 是 1000 x 2 的 slice
y := make([][]byte, 1000)
for i := range y {
y[i] = make([]byte, 2)
}
fmt.Println(len(y), len(y)*len(y[0]))

output 是:

1
2
2 2000
1000 2000

x 跟 y 都是多維 slice 而且都有 2000 個 element。x 只有兩個 slice、每個 slice 有 1000 個 element byte,y 有 1000 個 slice、每個 slice 只有兩個 element。

slice header 長這樣:

1
2
3
4
5
type SliceHeader struct {
Data uintptr
Len int
Cap int
}

from SliceHeader

在 64-bit 平台上,一個 slice header 的 size 是 24 bytes。那麼 x 的總 size 是 2000 + 2 * 24 = 2048 bytes。而 y 的總 size 則是 2000 + 1000 * 24 = 26000 bytes。

所以啦,考量 memory 使用的時候,需要注意多維 slice 是怎麼組的,像上面的 y,slice header 的 size 遠超過全部 element 的 size。

Inner array/slice length

With arrays of arrays, the inner arrays are, by construction, always the same length; however with slices of slices (or arrays of slices), the inner lengths may vary dynamically. Moreover, the inner slices must be initialized individually.

多維 array 的 inner array 都是相同 length 的。

多維 slice 的 inner slice 可以是不同 length。另外,inner slice 必須要各自 initialize。

Ref

如果要驗證 response「要有某個欄位」,在 api 定義(yaml 或 json 檔)裡該欄位要設 required

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
paths:
/general/test:
get:
summary: test
responses:
200:
description: test
content:
application/json:
schema:
type: object
properties:
id:
type: integer
username:
type: string
required: # 沒有這個的話, response 有或沒有這些欄位都會過
- id
- username

用 component 的版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
paths:
/general/test:
get:
summary: test
responses:
200:
description: test
content:
application/json:
schema:
\$ref: '#/components/schemas/User'
components:
schemas:
User:
type: object
properties:
id:
type: integer
username:
type: string
required: # 這裡
- id
- username

問題:Linux 下的 PhpStorm 無法切換輸入法輸入中文。

在 PhpStorm 啟動 script bin/phpstorm.shRun the IDE 前加入:

1
2
XMODIFIERS="@im=fcitx"
export XMODIFIERS

這樣用 shell 啟動 PhpStorm 就能切到中文輸入法(前提當然是已經有裝好了),不過從 launcher 啟動 PhpStorm 還是不行。

Dodkcer Get Started 隨手記。

Part 5: Stacks

A stack is a group of interrelated services that share dependencies, and can be orchestrated and scaled together. A single stack is capable of defining and coordinating the functionality of an entire application (though very complex applications may want to use multiple stacks).

stack 定義了一堆互相有關的 service,把 service 放在一個 stack 裡就可以一起操作、scale 等等。可以用一個 stack 來定義一個 application 的功能,比較複雜的 application 可能有多個 stack。

也就是可以把 application 拆分成多個 service 來運作,這些 service 可以只跑在一台機器上,也可以在 swarm 中跑在不同機器上。原本要做到這件事,要嘛要在一台機器上裝各種 server,要分散就得在多台機器上裝各自需要的 server,再不然就得裝 VM 起來做這些事,而且也沒有個統一的地方記錄各 service 的版本等等資訊(工人智慧…)。Docker 把 setup 開發及 production 環境跟 scale up 變得簡單很多。

More docker-compose.yml

part 3 寫的 docker-compose.yml 是定義只有一個 service 的 stack。下面是有兩個 service 分別為 visualizerredis 的 stack:

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
26
27
28
29
30
version: "3"    # compose file format version
services:
visualizer:
image: dockersamples/visualizer:stable
ports:
- "8080:8080"
# mapping host 的 file 到 container 裡
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
deploy:
# 指定 service 要 run 在哪個 node
placement:
constraints: [node.role == manager] # 只能 run 在 swarm manager
networks:
- webnet
redis:
image: redis
ports:
- "6379:6379"
volumes:
# mapping host 的 /home/docker/data 到 container 的 /data
- "/home/docker/data:/data"
deploy:
placement:
constraints: [node.role == manager]
command: redis-server --appendonly yes
networks:
- webnet
networks:
webnet:

如果沒有做 volume mapping,container 的 disk 的 file 在 container 重開後會不見。

其他 compose file 設定可參考:Compose file reference

修改 docker-compose.yml 後重新 deploy 只需 docker stack deploy -c docker-compose.yml <stack_name>

要結束整個 stack 的 service 則 docker stack rm <stack_name>

Part 6: Deploy your app

這篇是看你要用 Docker Enterprise 還是自己裝 Docker Engine - Community 進行開發及 deploy production。

開發環境跟 production 環境的 Docker 操作都一樣(只是執行的地方不一樣。

Dodkcer Get Started 隨手記。

Part 4: Swarms

這個 part 會 deploy app 到 cluster 並執行在多個 machine 上。

Linux 要先安裝 Docker Machine

Swarm

A swarm is a group of machines that are running Docker and joined into a cluster.

swarm 是一群 cluster 中跑著 Docker 的機器。機器加入 cluster 後,在上面執行的 Docker command 由 swarm manager 在 cluster 中執行。

The machines in a swarm can be physical or virtual. After joining a swarm, they are referred to as nodes.

一台加入 swarm 的機器稱為 node。

swarm 中只有 swarm manager 可以執行 command、讓其他機器加入 swarm 當 worker。worker 就只是執行,不能叫其他人做事或讓其他機器加入 swarm。swarm manager 可以用多種策略來執行 container。

平常 Docker 是在 single-host mode 執行,轉成 swarm mode 才能使用 swarm 相關功能。讓一台機器變成 swarm manager 後,Docker 會將 command 執行在它管理的 swarm 上,而非只在目前的機器。

Set up swarm

docker swarm init 把一台機器變成 swarm manager,再到其他要當 worker 的機器上執行 docker swarm join

接下來用 VM 建個 cluster。

因為用 VM,Linux 上要先裝 VirtualBox

使用 docker-machine 產生兩台 VM:

1
2
$ docker-machine create --driver virtualbox myvm1
$ docker-machine create --driver virtualbox myvm2

列出 VM:

1
2
3
4
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
myvm1 - virtualbox Running tcp://192.168.99.100:2376 v19.03.1
myvm2 - virtualbox Running tcp://192.168.99.101:2376 v19.03.1

接下來讓 myvm1 當 swarm manager、myvm2 當 worker。

透過 docker-machine ssh <vm> "<command>" 可以在 VM 上執行指令:

1
$ docker-machine ssh myvm1 "docker swarm init --advertise-addr <myvm1 ip>"

上面指令會得到將 worker 加入此 swarm 的 command,到 myvm2 上執行:

1
$ docker-machine ssh myvm2 "docker swarm join --token <token> <swarm manager ip>:<port>"

port 預設是 2377。

myvm1 看 node:

1
$ docker-machine ssh myvm1 "docker node ls"

最後 node 離開 swarm:

1
$ docker-machine ssh myvm2 "docker swarm leave"

docker-machine shell

docker-machine ssh 打起來很長,VM 也無法 access VM host 的檔案。用 docker-machine env <machine> 設定可以讓 shell 直接跟 VM 溝通:

1
2
3
4
5
6
7
$ docker-machine env myvm1
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.99.100:2376"
export DOCKER_CERT_PATH="/home/cjw/.docker/machine/machines/myvm1"
export DOCKER_MACHINE_NAME="myvm1"
# Run this command to configure your shell:
# eval $(docker-machine env myvm1)

執行 eval $(docker-machine env myvm1),接著用 docker-machine ls 確認目前 shell active 的 machine(ACTIVE column):

1
2
3
4
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
myvm1 * virtualbox Running tcp://192.168.99.100:2376 v19.03.1
myvm2 - virtualbox Running tcp://192.168.99.101:2376 v19.03.1

之後執行 docker 指令都是在 myvm1 上執行囉!比較方便而且可以 access 到 VM host 的檔案。要換到別的 machine 就再執行一次 docker-machine env <machine><machine> 換掉即可。

在 swarm manager 上 deploy app

跟 part 3 一樣,只是 part 3 是把本機直接變成 swarm manager,現在 swarm manager 是 myvm1(把它當作遠端)。

一樣用 docker stack deploy -c docker-compose.yml <stack_name> 來 deploy app。跟 part 3 不同的是現在 service 會分別開在 myvm1myvm2 上:

1
2
3
4
5
6
7
$ docker service ps getstartedlab_web
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
hprga9r6s5se getstartedlab_web.1 cjwind/get-started:part2 myvm2 Running Running 18 minutes ago
n1bc2c2xosal getstartedlab_web.2 cjwind/get-started:part2 myvm1 Running Running 18 minutes ago
rbkv7bctikfq getstartedlab_web.3 cjwind/get-started:part2 myvm2 Running Running 18 minutes ago
mr1o60i52f6u getstartedlab_web.4 cjwind/get-started:part2 myvm2 Running Running 18 minutes ago
4k7ehhz2nzl3 getstartedlab_web.5 cjwind/get-started:part2 myvm1 Running Running 18 minutes ago

到這裡完成了將 app deploy 到 cluster 上,並且 service 有 load-balanced!

access swarm 上的 app

可以透過 swarm 中任何一個 node 的 IP 去 access app。這是藉由讓 node 在 ingress routing mesh 裡達到。

[source]

在 swarm mode 打開前,swarm node 間的 port 7946 TCP/UDP 以及 port 4789 UDP 要開啟,ingress network 才能運作,所以要注意 app 不能佔用這些 port。

scale up in cluster

要讓 service 使用更多 node,只要先用 docker swarm join 增加 node,再 docker stack deploy,service 就能使用增加的 node。

Cleanup

清掉 docker-machine 的環境變數:

1
$ eval $(docker-machine env -u)

結束 stack 的所有 service、刪掉 stack:

1
$ docker stack rm <stack_name>

Dodkcer Get Started 隨手記。

Part 3: Services

Linux 要先安裝 Docker Compose,Windows 的 Docker Desktop 已經包含 Docker Compose。

Service

分散式系統中各 app 都可稱為一個 “service”,儲存資料到 DB、處理 video transcoding 等等都可以是個 “service”。

Docker 的 services 是一堆在 production 的 containers。一個 service run 一個 image,service 會指定這個 image 要怎麼執行──使用哪些 port、會跑多少 container 等等。只要改變執行的 container instance 數量就能 scale service。透過 docker-compose.yml 定義、執行跟 scale services。

docker-compose.yml

docker-compose.yml 定義 container 在 production 該有什麼行為。

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
26
27
version: "3"
services:
# 這個 service 叫作 web
web:
# 從 registry pull image
image: cjwind/get-started:part2
deploy:
# run 5 instances
replicas: 5
resources:
limits:
# 一個 instance 最多只能使用一個 single core CPU 10% 的 CPU time
cpus: "0.1"
# 一個 instance 最多只能用 50MB memory
memory: 50M
restart_policy:
# container fail 會立刻 restart
condition: on-failure
ports:
# map host port 4000 到 service web 的 port 80
- "4000:80"
networks:
# 讓 containers 透過 webnet 這個 load-balanced network 共用 host 的 port 80
- webnet
networks:
# 以預設值(load-balanced overlay network)定義 webnet
webnet:

執行 service

跑 service 前要先:

1
$ docker swarm init

接著執行:

1
2
$ docker stack deploy -c docker-compose.yml <stack_name>
$ docker stack deploy -c docker-compose.yml getstartedlab

可以用以下兩個 command 看執行的 service:

1
2
$ docker service ls
$ docker stack services <stack_name>

兩個 command 的結果都是:

1
2
ID                  NAME                MODE                REPLICAS            IMAGE                      PORTS
ylewu1idwhp0 getstartedlab_web replicated 5/5 cjwind/get-started:part2 *:4000->80/tcp

A single container running in a service is called a task.

列出 service 的 task:

1
2
$ docker service ps <service_name>
$ docker service ps getstartedlab_web

當然也可以用 docker container ls 看 service 的 running container。

列出 stack 所有 task:

1
2
$ docker stack ps <stack_name>
$ docker stack ps getstartedlab

service 執行時如果想修改 task 數量,只要修改 docker-compose.ymlreplicas 設定,再執行一次 docker stack deploy -c docker-compose.yml <stack_name> 就可以了!

要結束 service,首先關掉 app:

1
$ docker stack rm getstartedlab

離開 swarm:

1
$ docker swarm leave -f

Dodkcer Get Started 隨手記。

Part 1: Orientation and setup

Docker is a platform for developers and sysadmins to develop, deploy, and run applications with containers.

The use of Linux containers to deploy applications is called containerization.

Install Docker Engine on Debian

Images and containers

An image is an executable package that includes everything needed to run an application–the code, a runtime, libraries, environment variables, and configuration files.

A container is a runtime instance of an image–what the image becomes in memory when executed (that is, an image with state, or a user process).

Container vs VM

A container runs natively on Linux and shares the kernel of the host machine with other containers. It runs a discrete process, taking no more memory than any other executable, making it lightweight.

看起來 container 是 process level 的。

a virtual machine (VM) runs a full-blown “guest” operating system with virtual access to host resources through a hypervisor. In general, VMs provide an environment with more resources than most applications need.

用 Docker 可以把 application 需要的環境跟 code 等等包在 image 裡,以此 image 來開發跟 deploy,解決 application 的 system dependency 問題。因為整個環境都包在 Docker 裡了,application 可以在任何有 Docker 環境的地方執行(從 local 到 cloud 等等)。相較 VM,container 比較 light-weight 而且只有 application 需要的環境。

Part 2: Containers

hierarchy 由高到低:

  • Stack:define the interactions of all the services
  • Services:defines how containers behave in production
  • Container:a runtime instance of an image

Dockerfile

Dockerfile defines what goes on in the environment inside your container.

container 的網路跟 disk drive 是 virtualized、跟外界隔開的,所以要做 port mapping 以及指定哪些檔案要 copy 進 container。

Dockerfile 指令 [Ref]:

  • FROM 指定 parent image
  • WORKDIR 指定 container 內的 working dir
  • COPY copy 檔案到 container 裡
  • RUN 執行指令
    • The RUN instruction will execute any commands in a new layer on top of the current image and commit the results.

  • EXPOSE 表示 container 會 listen 哪些 port
    • docker run -p 做 port mapping 不同,不會真的 publish port
    • The EXPOSE instruction does not actually publish the port. It functions as a type of documentation between the person who builds the image and the person who runs the container, about which ports are intended to be published.

    • 寫不寫 EXPOSE 都可以 publish port,EXPOSE 是讓使用 image 的人知道 container 會 listen 哪個 port,好知道怎麼 publish port。
  • ENV 設定環境變數
  • CMD container 開起來時要執行的指令

Tips

  • 在 container 裡拿 hostname 會拿到 container ID

更多 docker run

  • -p <host port>:<container port> publish port,做 host port 跟 container port 的 mapping。[Ref]
  • -d 以 detach mode 執行 container,就是背景執行啦~

基本 docker run 使用 Ref

Share image

A registry is a collection of repositories, and a repository is a collection of images

docker 預設使用的 registry 是 Docker Hub

tag 是用來給 image 版本的方式。

  1. 幫 image 加上 registry 的 repository tag:
1
2
$ docker tag <image> <username>/<repository>:<tag>
$ docker tag <imageID> cjwind/helloworld:0.1.0
  1. publish image:
1
2
$ docker push <username>/<repository>:<tag>
$ docker push cjwind/helloworld:0.1.0

如果是 private registry,要先執行 docker login

  1. pull and run image from remote repository:
1
2
$ docker run <username>/<repository>:<tag>
$ docker run cjwind/helloworld:0.1.0

如果 local 不存在 image,Docker 會從 repository pull image 下來再執行。

跟主題無關的 murmur:來嘗試一種筆記方式,以「解決某個問題」或「達到某個目標」為主,記錄中間的試驗跟操作等過程。因為是過程記錄,路途中可能歪掉(?)、出現好像有關但最後跟解決方式無關的東西。


目標

目標是 Win10 下的 PhpStorm 可以用不同版本 PHP 執行程式。

一個方式是裝多個版本的 PHP (覺得把環境弄得很亂不蘇胡),既然 PhpStorm 的 CLI Interpreter 可以用 Docker,乾脆來玩一下 Docker~

雖然是在 Win10,但除了安裝 Docker 的版本不同,其他 Docker 操作基本在 Linux 應該是可以用的(畢竟這沒有牽涉到更細節的什麼 linux container、windows container 之類的,我也不確定那有沒有關係)。

Win10 Docker Installation

google it,裝完就忘了,大概是 Hyper-V 要開、Win10 要某個版本之後,然後去裝 Docker for Windows,可以參考這篇

Docker 的 Image & Container 極簡概念

  • image:類似 VM image 的東西,而且 image 可以一層層疊起來。
  • container:依據 image 開起來的 instance,container 的環境是互相隔離的。
Read more »

一般 #define macro 不會將 parameter 展開成字串,只會把 parameter 放到對應位置,例如:

php
1
2
<?php
echo "Hello world";
1
2
3
4
5
6
#define ECHO(str) printf("%s\n", str)
int main() {
char s[] = "hello";
ECHO(s);
return 0;
}

經過 preprocess:

1
2
3
4
5
6
7
$ cpp stringify.c

int main() {
char s[] = "hello";
printf("%s\n", s);
return 0;
}

如果在 parameter 前加 #,preprocessor 會把 actual argument 變成字串,稱為 Stringizing。用個例子說明:

1
2
3
4
5
6
#define ECHO(str) printf("%s\n", #str)
int main() {
char s[] = "hello";
ECHO(s);
return 0;
}

經過 preprocess(只是例子,code 本身不太 make sense):

1
2
3
4
5
int main() {
char s[] = "hello";
printf("%s\n", "s");
return 0;
}

Linux kernel 使用這個技巧將 macro 展開成字串。

__stringify 定義在 include/linux/stringify.h

1
2
#define __stringify_1(x...)	#x
#define __stringify(x...) __stringify_1(x)

為什麼 __stringify 要 define 兩次呢?

Argument Prescan 提到 macro 的參數如果也是 macro,一般在被替換進 macro body 前就會被展開,但如果是 stringized 則不會被展開。

1
2
3
4
5
6
#define __stringify_1(x...) #x
#define __stringify(x...) __stringify_1(x)
#define FOO bar

__stringify_1(FOO) // become "FOO"
__stringify(FOO) // become "bar"

這例子裡 __stringify_1(FOO) 因為是 stringized 的 macro 參數,所以 FOO 不會被展開,macro 替換後最後變成 "FOO"。而 __stringify(FOO) 會先展開 FOO 並替換,變成 __stringify_1(bar),接著再 scan 一次將 macro 展開為 "bar"

__stringify define 兩次是為了讓參數可以使用 macro。像上面的例子,通常期望 __stringify(FOO) 得到 "bar" 而非 "FOO"

... 是跟 function 一樣的不定參數,可參考 Variadic Macros