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.

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
  • WORKDIP 指定 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
$ docker tag <image> <username>/<repository>:<tag>
  1. publish image:
1
$ docker push <username>/<repository>:<tag>
  1. pull and run image from remote repository:
1
$ docker run <username>/<repository>:<tag>

如果 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 的環境是互相隔離的。
(More...)

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

stringify.c
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

如何測試 event 裡提到 event 的測試有兩個方面,其中之一是測試 event 是否有被正確 trigger,有些 test framework 有檢驗 event 是否 trigger(或稱 emit)的驗證。(我應該是在某個 js test framework 看到的)

現在工作是用 Yii 這套 PHP framework,配合的 test framework 是 Codeception。

Yii2 有它的 Event 機制,看了下 Codeception 的 Yii2 module 沒有提供 event trigger 的驗證,上星期無聊就自己寫了個 codeception-yii2-event-tester 啦。

實作方面沒什麼困難,都在搞怎麼包 composer package 跟搞定 Travis。

Problem

Travis 在 run composer install 會出現類似以下錯誤:

1
2
3
4
5
Failed to clone the git@github.com:jquery/jquery-dist.git repository, try running in interactive mode so that you can enter your GitHub credentials

[Composer\Repository\InvalidRepositoryException]
No valid bower.json was found in any branch or tag of https://github.com/jq
uery/jquery-dist.git, could not load a package from it.

失敗的 repository 不一定,可能這次 jquery 下次別的。

找了一陣,說是踩到 Github 的 rate limit(我理解是 composer install 會一直從 github 抓東西所以容易踩到),但又有文章說這個問題已經被修正。我還是踩到了啊

Solution

  1. 在 Github 產生 Personal access token
  2. 在 Travis 設定有使用 Travis 的 project 的環境變數(environment variable),指定變數名稱,值是剛剛在 Github 產生的 access token。
  3. 修改 .travis.yml,在 composer install 前加入 composer config github-oauth.github.com ${環境變數名稱}

Ref

測試 event 分成兩邊:

  • 測試 event listener:有沒有在 event 發生後做該做的事。
  • 測試 trigger event:有沒有正確 trigger event。

基本概念是驗證一方時假造另一方。

測試 event listener

檢查 event 有沒有註冊到 event listener。用「event 發生後 event listener 有沒有做該做的事」來驗證,而不是試圖 access class 的內部 event 資訊來看是否有註冊成功。例如發生 event 後系統某些狀態會改變,測試方式是在測試裡 trigger event(假造 trigger event),驗證系統狀態是否有正確改變。

如果發生 event 後會 call 某些 third party function,測試方式是先做個 mock object、inject mock object 到被測試 class,接著 trigger event,最後驗證 mock object 是否有被 call 到該 call 的 function。

測試是否有 trigger event

在測試中給要測試的 event 註冊一個假的 event listener,接著讓被測試 class 做應該要 trigger event 的事情,最後驗證假 event listener 有沒有被 call 到。

驗證假 event listener 有沒有被 call 到不一定要用 mock object,也可以是假 event listener 在被 call 到時修改 test 裡的變數,最後直接驗證該變數,這跟語言支援有關。

test framework 支援

有些 test framework 的支援「某個 event 是否有被 trigger 或 emit 的驗證」。

Download kernel source code

我用的 kernel source code 是從 https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git clone 的,寫這篇的版本是 5.0.0-rc3

1
$ git clone https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git linux-stable

Add system call

首先在 kernel source code root 建立一個資料夾 mysyscall/

1
2
$ cd linux-stable
$ mkdir mysyscall

新增 mysyscall/hello.c、加入新的 system call sys_hello()(不可免俗的來 hello world 一下):

mysyscall/hello.c
1
2
3
4
5
6
#include <linux/kernel.h>

asmlinkage long sys_hello(void) {
printk("Hello Kernel World!\n");
return 0;
}
(More...)