---
# System prepended metadata

title: Container
tags: [分散式系統, essay, Scaling]

---

{%hackmd SybccZ6XD %}
<style>
.toc > ul > li:first-child {
  list-style: none;
}
.toc > ul > li:first-child > a {
  display: none;
}
</style>

Container
===

###### tags: `essay` `分散式系統` `Scaling`

> [name=Shueh Chou Lu][time=July, 2021]

[TOC]

---

## 服務從一個變多個

問題往往就發生在當服務從一個變成多個時。

### Protocol

**JSON** - 當你只有一個 API Endpoint，足矣。

>但是當你有越來越多 Endpoint 甚至同一時間需要各個 Endpoint 的資源...

**GraphQL** - 只需要對應一個端點，就可以獲得多個 API 的資訊且可以自行組織查詢內容

> 但是 API 的行為越來越複雜，牽涉到多種邏輯...

**gRPC** - 只需要專心撰寫程式邏輯，相關的 HTTP client 皆已經處理完。

### SLA and Load Test

怎麼確保產品在經過多次傳輸後仍能維持可預期的保證

1. SLO
2. Load Test
3. Baseline

### Observability

當產品越來越多，怎麼快速精準的獲取應用、服務、設備的資訊。

- Log
- Metric
- Tracing
- Alert
- Health Check

## 部署方式

有很多種方法可以提升服務的數量，各種方式都需權衡：

- 可攜帶性 - 相依套件須一併攜帶，包括 Binaries Depending
- 效率 - 每次部署需消耗的成本
- 安全性 - 機敏資訊的暴露
- 脆弱性 - 相依套件版本的衝突等

### 比較

![](https://i.imgur.com/eaViL43.png)

- 實體機 - 若和其他服務（如 Load Balancer）有套件的衝突，會很麻煩。
- VM - 適合 DB 和其他需要儲存穩定狀態的應用程式。
- Container - 適合高迭代、高部署率的應用程式。

> Container 包括 Image、Port Mapping、Volume Mappings
> Volume 就是管理 Container 端和 Host 端的 filesystem

## Docker

Docker 是一個管理、建置 Container 的工具，其主要有三個工具：

1. *dockerd* daemon，對外提供 API，進行 Container 的建置和管理
2. *Docker CLI* 提供語法對 API 進行呼叫和設定。
3. *Docker Hub*

> *Docker Desktop*
> Docker 是執行在 Linux 之上的工具，若機器不是 Linux，就會需要 *Docker Desktop*。
> 其不僅可以建立 VM 來模擬 Linux 環境，也提供其他好用功能，如 UI 和 Kubernetes 的整合。


```mermaid
graph TD

  subgraph Host OS
    Filesystem
  end

  subgraph Container
    0[Image]
    Volume --> Filesystem
    Filesystem --> Volume
    Port
  end
  1[Docker] --> 0
  0 --> 1
  2[Daemon] -- HTTP --> 1
  3[Docker CLI] --> 2
  4[Docker Hub] --> 2
  2 --> 4

```

### Docker Image

把應用程式包裝成 Image 再依此建立 Container。
應用程式大部分都需要建立在特定環境之上，以 Node.js 的應用程式為例：

```mermaid
graph TB
  subgraph Linux
    0["/usr/bin"]
  end
  subgraph Node.js
    1["/usr/bin/node"]
  end
  subgraph Application
    2["/srv/server.js"]
  end
  0 --> 1
  1 --> 2
```

> 每個環境都稱為一組 stage，如 `Linux`、`Node.js` 和 `Application`。
> 每個 stage 中會有很多層 layer，分別代表建立過程的一個步驟。

以 Node.js 並包裝在 Alpine 之上的 Image 為例：

```bash=
$ docker pull node:lts-alpine
c9b1b535fdd9: Pull complete
750cdd924064: Downloading [==>              ]  2.485MB/34.53MB
2078ab7cf9df: Download complete
02f523899354: Download complete
```

| # | Layer | Size |
|-|-|-|
|1| ADD file ... in /... | 2.69 MB |
|2|CMD \[\"/bin/sh\"\]|0 B|
|3|ENV NODE_VERSION=14.17.3|0 B|
|4|/bin/sh -c addgroup -g 1000 ...|34.53 MB|
|5|ENV YARN_VERSION=1.22.5|0 B|
|6|/bin/sh -c apk add --no-cache ...|2.14 MB|
|7|COPY file ... in /usr/...|281 B|
|8|ENTRYPOINT \[\"docker-entrypoint.sh\"\]|0 B|
|9|CMD ["node"]|0 B|


> Image 大小為 39.35 MB，[連結](https://hub.docker.com/layers/node/library/node/lts-alpine/images/sha256-5c33bc6f021453ae2e393e6e20650a4df0a4737b1882d389f17069dc1933fdc5)

> 有改變 filesystem 的步驟（會改變 Image 大小），才會列出來。

> SHA 值計算方式是上一個 SHA 值加上現行的指令組出來的。

若改為 Debian（預設值）則為：

```bash=
$ docker pull node:lts
199ebcd83264: Pull complete
ddbb155879c0: Pull complete
c194bbaa3d8b: Pull complete
6154ac87d7f3: Pull complete
0c283e88ced7: Pull complete
dba101298560: Pull complete
1d8bfd4e555f: Downloading [=====>       ] 7.56MB/33.38MB
757e41ffbdcc: Download complete 
6e055c4b8721: Download complete
```

| # | Layer | Size |
|-|-|-|
|1|ADD file ... in ... |43.28 MB|
|2|CMD \["bash"\]|0 B|
|3|/bin/sh -c set -eux; apt-get ...|10.77 MB|
|4|/bin/sh -c set -ex; if ...|4.14 MB|
|5|/bin/sh -c apt-get update && ...|47.46 MB|
|6|/bin/sh -c set -ex; apt-get ...|204.5 MB|
|7|/bin/sh -c groupadd --gid 1000 ...|4.09 KB|
|8|ENV NODE_VERSION=14.17.3|0 B|
|9|/bin/sh -c ARCH= && dpkgArch="$(dpkg ...|33.38 MB|
|10|ENV YARN_VERSION=1.22.5|0 B|
|11|/bin/sh -c set -ex ...|2.23 MB|
|12|COPY file ... in ...|295 B|
|13|ENTRYPOINT \[\"docker-entrypoint.sh\"\]|0 B|
|14|CMD \["node"\]|0 B|

> Image 大小為 345.75 MB，[連結](https://hub.docker.com/layers/node/library/node/lts/images/sha256-c31c7a8a41bcef8e260953bae91e65839f2cc29e5fbaa89407859ff7b7f144e3)。

> Container 部署實作上仍建議以較小的 Image 為基底，除了加速部署流程外，較小的檔案面積理論上也能降低被攻擊的機會。


![](https://i.imgur.com/eaViL43.png)

### 實作

相信了解 Image 製作原理後，對於實作就不會有太大問題，這裡有實作相關的文件，有興趣可以去查看

- [以 Node.js 為基礎的製作範例](https://hackmd.io/FUJbi-ScTeaH0cg69G-9EA)
- [各語言的範例](https://docs.docker.com/language/) - 官網

### 和 Container 的互動

透過 Docker CLI 可以和 Container 互動，除了讓我們了解其內部運作過程，也可以幫助我們 Debug。

#### 進入 Container

```bash=
$ docker run -it ubuntu /bin/bash
```

- `-i` 代表要以互動模式進入 Container
- `-t` 代表是以 TTY session 進入，和 `-i` 整合為 `-it`
- 並使用 `bash`

進入後，即可透過 `ps -e` 輸出所有 process。

```
PID TTY          TIME CMD
  1 pts/0    00:00:00 bash
 10 pts/0    00:00:00 ps
```

> bash 作為初始行為，必須在 PID 1。

由此可看出，Container 的行為相當單純，不像實際使用的電腦，會有各種服務在背景運作。

#### 對 Container 內部下達指令

`docker exec ubuntu /bin/ls /var`

#### 把檔案丟進 Container 中

```bash=
$ curl -o index.html http://example.org
$ docker run --rm -p 8080:80 \
  -v $PWD:/usr/share/nginx/html nginx
```

> `-p` 即為前面提到的 port-mappings
> `-v` 即為 volume-mappings（filesystem）

### Alternative

現在已經較少其他管理 Container 的產品：

- RedHat 有一個已經 archive 的專案 [rkt](https://github.com/rkt/rkt)
- [Open Container Initiative](https://opencontainers.org/) 則嘗試制訂 container 標準。

## 分散式系統上的應用

多個服務要怎麼透過 Docker 建立？有幾個方法：

- `docker run exmaple/server1` `docker run exmaple/server2`

> 多次 `docker run exmaple/server` 去建立不同的應用程式會難管理且容易出錯。

- 把不同服務包裝成同一個 Image

> 有時相似的應用程式可以包裝成同一個 Image，如上一次報告提到的 `ELK`，但是分散式系統上不同的應用程式若包裝成同一個 Image 會讓服務的獨立性消失。

docker-compose 便是用來解決這一問題的方案。

以上一份 Tracing 中的 Zipkin 為例：

```mermaid
graph LR
  0[Client]
  1[Zipkin]
  subgraph APP
    2[web-api]
    3[recipe-api]
  end
  
  0 --> 2
  2 --> 3
  3 --> 2
  2 -- info --> 1
  3 -- info --> 1
```

就可以依此建立 docker-compose.yaml：

```yaml=
version: "3.7"
services:
  zipkin: 
    image: openzipkin/zipkin-slim:2.19 
    ports: 
      - 9411:9411
  recipe-api:
    build: 
      context: ./recipe-api
      dockerfile: Dockerfile-zipkin
    ports:
      - 4000:4000
    environment: 
      ZIPKIN: zipkin:9411
    depends_on: 
      - zipkin
  web-api:
    build:
      context: ./web-api
      dockerfile: Dockerfile-zipkin
    ports:
      - 3000:3000
    environment:
      TARGET: recipe-api:4000
      ZIPKIN: zipkin:9411
    depends_on:
      - zipkin
      - recipe-api
```

## Docker Registry

Docker Hub 提供存放 Image 空間，但對於私人空間的存放數量有限制。
可以根據預算，考慮其他 Docker Registry，例如：

- JFrog Artifactory
- Cloud based

## 註

這次分享省略

- 上傳 Image 到 Docker Registry
- 自製 Docker Registry UI