LFS253: Containers Fundamentals
Tags: lecture, lfs253, container, shortnote
Published: 2020-01-07 10:00:20

# Index

- [What's Containers?](#chapter-1)
- [The History of Containers](#chapter-2)
- [Building Block](#chapter-3)
- [Container Runtimes](#chapter-4)
- [Docker for Mac and Windows](#chapter-5)
- [Accessing a Remote Docker Daemon](#chapter-6)
- [Container Operations](#chapter-7)
- [Working with Image](#chapter-8)
- [Working with Dockerfiles](#chapter-9)
- [Container Networking](#chapter-10)
- [Container Storage](#chapter-11)
- [Build a Multi-Container Application with Docker-Compose](#chapter-12)
- [Introducing Docker APIs](#chapter-13)
- [Docker Tips and Tricks](#chapter-14)

<a name="chapter-1"></a>

---

# Chapter 1: What's Containers?

### What are Containers ? 

> Containers is an **application-centric way** to deliver high-performance, scalable application in the infrastructure of your choices

ปัญหาที่กวนใจข้อสำคัญของทั้ง developer และ operation ในการทำงานร่วมกันก็คือ dependencies 

แล้ว dependencies คืออะไรเหรอ? มันคือ ส่วนอื่นๆ ที่ไม่ใช่ code หลัก แต่ code หลักต้องเรียกใช้เพื่อให้ application สามารถทำงานได้ นั่นก็คือพวก libraries, configuration หรือ แม้กระทั่ง OS เป็นต้น 

เรามักพบปัญหาว่า developers develop application ขึ้นมาทำการและทำการ test บน laptop ของตัวเองเรียบร้อยแล้ว แต่พอเอามาขึ้นที่ production กลับใช้งานไม่ได้

ทำไมใช้ไม่ได้ล่ะ 🤬!@#@$!#% 

อาจเป็นไปได้ทั้งใช้ interpreter คนละ version กัน มี library ไม่ครบหรือ version ไม่ตรงกัน หรือ แม้กระทั้ง developers develop พบ Mac แต่ production เป็น Linux

ปัญหาเหล่านี้ pain-in-the-ass ของทุกคนมาเป็นเวลาอันยาวนานมาก

จน linux kernel ก็ออก features ที่สามารถแบ่ง virtual environment ออกเป็นชุดย่อยๆ เพื่อ run แต่ละ application ได้ พร้อมทั้งสามารถ pack dependencies ต่างๆ เข้าไปได้อีกด้วย

containers จึงค่อยๆ ถือกำเนิด และพัฒนาต่อมาเรื่อยๆ จนถึงปัจจุบัน และยังคงพัฒนาต่อไปเรื่อยๆ

### ทำไมถึงต้องมี containers ?

1) เพื่อให้การ develop และ การ deploy application ได้รวดเร็วทันต่อตลาดและความต้องการของลูกค้า 
2) ถ้าเรามี innovation อะไรแล้ว develop บน containers เราสามารถใช้มัน run ใน production ได้เลย
3) ในการ patch fix bug หรือเพิ่ม feature ถ้าเกิด deploy ไปแล้วมีปัญหาเราสามารถ rollback ไปใช้ container version เก่าที่ใช้งานได้ได้เลย เพราะทุกอย่างอยู่ใน container อยู่แล้ว
4) คุ้มค่า เพราะสามารถ run ได้หลาย app ใน 1 เครื่อง โดยไม่ต้องห่วงเรื่อง dependencies

และอื่นๆ อีกมากมาย

### Containers ไม่ใช่ Light-weight VMs

แต่สิ่งที่มันเหมือนกันคือ สามารถแยก environment และ resource เพื่อให้ application run เท่านั้นเอง

|![Containers vs VMs](https://thepracticaldev.s3.amazonaws.com/i/z05fp2dy8v42g9hijkfd.png)|
|--| 
|<center><sup><sub>*[Containers versus Virtual Machines](https://pawseysc.github.io/container-workflows/01-docker-intro/index.html)*</sub></sup></center>|

จากภาพ 
- **VMs** run บน **Guest OS** ซึ่ง run อยู่บน **Hypervisor**
- **Containers** run บน **Container Engine** ซึ่ง run อยู่บน **Host OS**

> เราไม่สามารถ run Windows application ใน Container ที่ run อยู่บน Linux Host OS แต่ VM สามารถทำได้

### เราจะ deploy containers ได้ที่ไหนบ้าง?

เราสามารถ run container ได้ทั้งบน VM และ Bare-Metal 

แต่ run ที่ไหนจะดีกว่าล่ะ ?

- ในมุมมองของ **Containers** 🡆 ยังไงก็ได้ไม่ต่างกัน
- ในมุมมองของ **IT Department** 🡆 ขึ้นอยู่กับหลายปัจจัย เช่น stability, performance, ค่าใช้จ่าย และ ทักษะของผู้ปฏิบัติงาน เป็นต้น

> Containers สามารถ scale ได้โดยใช้ **container orchestrator software** เช่น Docker Swarm, Kubernetes, Mesos Marathon, Nomad, Amazon ECS, etc

<a name="chapter-2"></a>

---

# Chapter 2: The History of Containers 

### History

- Containers ไม่ใช่ของใหม่ มันเริ่มมาจาก **File system container** ที่เรียกว่า **chroot** ซึ่งมาพร้อมกับ Unix 7<sup>th</sup> Edition ซึ่งออกมาในเดือนมีนาคม ปี 1982
- ในปี 2000, Free BSD ได้เพิ่ม feature ให้ chroot แล้วเปลี่ยนชื่อเป็น **jail**
- ในช่วงแรกๆ ของทศวรรษ 2000, Solaris ปล่อย **zone** ซึ่งเป็น feature ในการแบ่ง OS ออกเป็นส่วนย่อยๆ (Virtualized Operation System Services)

### chroot

> **Chroot** is an operation that changes the apparent root directory for the current running process and its
children. A program that is run in such a modified environment cannot access files and commands
outside that environmental directory tree. This modified environment is called a chroot jail.

chroot เป็นการทำให้ application ที่เรา run ไม่เห็น path `/ (root)` จริงๆ แต่เป็น `/ (root)` ปลอมที่ถูกสร้างขึ้นเพื่อให้ แต่ละ process และ child process ของมัน เห็น

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/c2cy0ukfrown3oc356ft.jpg)

- `/ (root)` ขอแต่ละ process จริงๆ แล้ว ถูก mount ไว้ที่ `/mnt`
- เราสามารถ demo chroot ได้ด้วย `debootstrap` ซึ่งสามารถ install ใน Debian ได้

> ถ้าใช้ `chroot`:<br/> process ที่ถูกแบ่งแค่ file system นอกนั้น share กันใน 1 server
> ถ้าใช้ `jail`:<br/> process ที่ถูกแบ่งทั้ง file system, users, network, sub-system และอื่นๆ

### systemd-nspawn

systemd-nspawn ใช้ systemd เป็น init system ในการแบ่ง userspace และ จัดการ process ต่างๆ 

> เราสามารถใช้ systemd-nspawn ในการ launch container ได้ โดย systemd-nspawn เปรียบได้กับ **chroot** + **virtualized process tree, users, host and domain name, etc**


<a name="chapter-3"></a>

---

# Chapter 3: Building Block 

### 3 Building Blocks of Containers

1) Namespaces

namespaces ถูก release มาพร้อมกับ linux kernel version 2.4.19 ในปี 2002 โดย ทำหน้าที่ในการ virtualized system resources ดังต่อไปนี้ ให้แต่ละ process

- **mount point (mnt)**: `เป็นการ virtualized mount point คล้ายๆ กับ chroot`
- **process id (pid)**: `เป็นการ virtualized pid โดยในแต่ละ namespace จะมี pid ซ้ำกันได้ โดย pid ในแต่ละ namespace จะถูก map เป็น pid ของ host อีกที`
- **network (net)**: `เป็นการ isolate /proc/net, IPs, interfaces, และ routes ตามแต่ละ namespaces`
- **user id (users)**: `เป็นการ isolate User และ group IDs ตามแต่ละ namespaces`
- **inter-process communication (ipc)**: `เป็นการ isolate SystemV IPC และ POSIX Message Queues ตามแต่ละ namespaces`
- **UTS (hostname)**: `เป็นการ isolate the hostname และ NIS (Network Information Service) name ตามแต่ละ namespaces`

2) Control Group (cgroups)

cgroups ถูก release มากับ linux kernel ในปี 2007 โดยทำหน้าที่ในการจำกัดการใช้งาน system resources เช่น 

- **CPU (cpu/cpuset)**: `ควบคุม time period และ upper limit ในการใช้งาน cpu รวมทั้ง assign งานในแต่ละ group`
- **Memory (memory)**: `ควบคุม maximum memory limit, memory swappiness, OOM, etc`
- **disk i/o (blkio)**: `assign value of block I/O รวมถึงกำหนดค่า hard limit ของ block I/O`
- **network (net_cls/net_prio)**: `tags network packets, prioritize และ weight traffic ขาออกของ network interface`
- **devices**: `ควบคุมการ assign device ไปยัง cgroup`
- **hugepages**: `ควบคุม hugepages size usage และ จัดการ hugepages matrices ในแต่ละ cgroup`

ในแต่ละกลุ่มของ process

> มี cgroup พิเศษชื่อ `Freezer` สำหรับใช้ในการ suspends/resumes tasks ใน group

3) UnionFS

UnionFS เป็นการรวมเอา file system อื่นๆ มารวมกันเป็นเสมือน file system เดียว โดยแต่ละ file system ที่เอามารวมกัน จะเรียกว่า branch สามารถ set priority และ กำหนดให้เป็น mode read-only ได้

![UnionFS](https://thepracticaldev.s3.amazonaws.com/i/9qpq9i9ifuxubvseknir.jpg)

<a name="chapter-4"></a>

---

# Chapter 4: Container Runtimes

### History of Container Runtimes

- ในตอนแรก Docker ใช้ **lxc** ในการ ติดต่อกับ linux container จากนั้นจึงเปลี่ยนมาใช้ **libcontainer**
- เดือนธันวาคม 2014, CoreOS ออก Container Runtimes ชื่อ **Rocket (rkt)**
- เดือมมิถุนายน 2015, มาตรฐาน **OCI (Open Container Initiative)** ถือกำเนิดขึ้น ภายใต้การกำกับดูแลของ Linux Foundation เพื่อป้องกันไม่ให้เกิดปัญหา vendor lock-in ในส่วนของ runtime และ image specification
- หลังจากนัน **runC** ถือกำเนิดขึ้นตามมาตรฐานของ OCI โดย นำเอา code ของ **libcontainer** มาแก้ไขเพิ่มเติม

### RunC

- เป็น container runtime ที่พัฒนาขึ้นมาตาม spec ของ OCI
- มี command line ของตัวเองในการจัดการ container ได้
- สามารถ embedded ไปใน software อื่นๆ ได้
- compatible กับ Docker 
- ไม่ create daemon
- สามารถทำงานร่วมกับ systemd ได้เป็นอย่างดี

### Docker

- Docker เป็นมากกว่า Container Runtime เพราะมันทำได้ทั้ง dev, ship และ run application ได้
- Core ของ docker คือ **Docker Engine** โดยมันจะทำหน้าที่ไป สั่ง containerd เพื่อจัดการ runC ที่ run container

|![Docker](https://thepracticaldev.s3.amazonaws.com/i/yc6mudxnlowt251f0toz.png)|
|:--:|
|<center><sup><sub>Source: [docker.com](https://www.docker.com/blog/docker-engine-1-11-runc/)</sub></sup></center>|

- Docker มีโครงสร้างการทำงานเป็น client-server แบบ Restful API

|![client-server](https://thepracticaldev.s3.amazonaws.com/i/0dd898rl1iw5ztaa4rcr.png)|
|:--:|
|<center><sup><sub>Source: [www.researchgate.net](https://www.researchgate.net/figure/High-level-overview-of-Docker-architecture_fig1_308050257)</sub></sup></center>|

- Docker Product
  - Community Edition (CE) - Free
      - `edge`: release ทุกเดือน
      - `stable`: release ทุก 3 เดือน
  - Enterprise Edition (EE) - Paid
      - integrated และ supported กับ certified container platform
      - release ทุก 3 เดือน / แต่ละ release จะ support เป็นเวลา 1 ปี
      - มี 3 tier ได้แก่ basic / standard / advance
- การนับ version ของ Docker
  - ก่อน มีนาคม 2017, จะนับแบบ `Semantic Versioning` คือ Major.Minor.Patch
  - หลังจากนั้น, จะนับแบบ `Calendar Versioning` คือ YY.MM-ce และ YY.MM-ee

> APIs version ของ Docker จะนับแยกจาก Docker version โดยเริ่มมี inter-portability มาตั้งแต่ Docker 1.13

### Rocket (rkt)

- ในปี 2014, CoreOS launch **App Container (appc)**
- ในปี 2016, launch **rkt** ซึ่ง implement ต่อมาจาก **appc**
- จากนั้นเมื่อ OCI ถือกำเนิด, **rkt** ถูกปรับให้ compatible กับ OCI
- **rkt** ทำงานรวมกันได้ดีกับ `systemd`
- **rkt** ใช้ image format ใหม่ชื่อ **Application Container Image** ซึ่งประกอบด้วย 2 ส่วนสำคัญคือ
    - `rootfs`: เป็นที่เก็บ files ต่างๆ ที่ต้องใช้งาน
    - `Image Manifest`: เป็น configure ที่เก็บ execution parameters และ resource limit ต่างๆ
- **rkt Architecture**

|![rkt-flow](https://thepracticaldev.s3.amazonaws.com/i/uw9m9sns9kov31jqkxwh.png)|
|:--:|
|<center><sub><sup>Source: [CoreOS.com](https://coreos.com/rkt/docs/latest/devel/architecture.html)</sup></sub></center>|

  - `stage0`: discovers, fetches และ จัดการ application image
  - `stage1`: เตรียม execution engine สำหรับการ launch containers
  - `stage2`: เตรียมแบ่งแยก virtual environment สำหรับแต่ละ application

<a name="chapter-5"></a>

---

# Chapter 5: Docker for Mac and Windows

### History

- ก่อนที่จะเป็น Native Support การใช้ docker บน windows หรือ Mac ต้องใช้ตัวช่วยที่ชื่อว่า **Docker Toolbox** ซึ่งก็คือการลง Virtualbox แล้ว run linux VM ขึ้นมา 1 ตัว จากนั้นคำสั่งต่างๆ ที่เราส่งไป ก็จะูกส่งไปที่ linux VM นั่นๆ โดยส่วนประกอบของ "Dcoker Toolbox" มีดังนี้
    - Virtualized Environment
    - boot2docker VM
    - kitematic
    - Docker Engine
    - Docker Compose
    - Docker Machine
- จากนั้นเดือนกรกฎาคมปี 2016 Docker จึงเริ่มออก Native Support สำหรับ Windows และ Mac
    - **Mac**: ใช้ Hardware Virtualization ในระดับ OS และ Unikernel Technology ที่เพิ่งไป acquire มา
       - ใช้ light-weight hypervisor แล้ว run linux instance ที่มี Docker Engine
       - จากนั้น map network และ Disk ของ container กับ linux instance
       - ซึ่ง linux instance นั้น map resource อยู่กับ host
    - **Windows**: มี 2 techniques ให้เลือก
       - **Windows server containers** -> เป็นการ share kernel กับ host เลย โดย kernel ของ Windows สามารถแยก namespaces และ cgroups ได้เหมือน Linux
       - **Hyper-V Containers** -> run "Windows server containers" ใน optimized VM ที่ใช้ Hyper-V เป็น Hypervisor
      
> **Prerequisite for Windows**: Hyper-V และ Virtualization ต้อง enable ไว้ และต้องใช้ windows 10 pro, enterprise หรือ education แบบ 64 bit

### Unikernel

Unikernel จะทำให้เราสามารถสร้าง image เฉพาะทางได้ โดยเราสามารถเลือกเฉพาะ library ที่ application ต้องการไป compile รวมกับ แล้ว build เป็น machine image เพื่อนำไป run บน hypervisor หรือ บน bare-metal เลยก็ได้ เช่น ถ้า app ของเราต้องการติดต่อกับโลกภายนอก ก็ เลือกเฉพาะ network library ไป build รวมกับ app ของเรา

<a name="chapter-6"></a>

---

# Chapter 6: Accessing a Remote Docker Daemon

### Docker Client and Docker Host Communication

|![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/0mzzi9lc0d5qggo6yo86.jpg)|
|:--:|
|<center><sub><sup>Source: [Linux Foundation](https://www.linuxfoundation.org/)</sup></sub></center>|

Docker Client เป็น command ที่เรา run เพื่อไปสั่ง Docker Daemon ที่ run อยู่ที่ Host ต่างๆ โดย Docker Client 1 ตัว สามารถติดต่อได้กับ Docker Daemon ได้หลายตัว

โดย default, Docker daemon จะ listen ที่ Unix Socket เท่านั้น (เครื่องอื่นไม่สามารถ connect ได้) การที่จะให้ Docker daemon เปิดรับ connection จากภายนอกได้ ต้อง start ด้วย option `-H` เช่น `-H 0.0.0.0:2375`

การ connect ไปยัง Docker daemon ก็ใส่ `-H <IP>:<PORT>` ไปด้วยเช่นกัน ดังนี้ `docker -H tcp://1.x.x.x info` (ถ้าไม่ระบุ port default คือ 2375 (unencrypted) และ 2376 (encrypted))

อีกวิธีหนึ่งคือการประกาศ environment variables `DOCKER_HOST` เช่น `export DOCKER_HOST=1.x.x.x` จากนั้น `docker info` ได้เลย โดยไม่ต้องระบุ host

### Docker Machine

`docker-machine` เป็น tool ในการ provision Docker บน เครื่องของตัวเองหรือ cloud provider ต่างๆ เช่น AWS, GCP, Digital Ocean และ อื่นๆ

นอกจากนั้นยังสามารถ access เข้าไปสั่งงาน docker daemon ที่สร้างขึ้นมาได้อย่างปลอดภัย

> docker-machine สามารถ download ได้จาก [Github](https://github.com/docker/machine)

<a name="chapter-7"></a>

---

# Chapter 7: Container Operations

### What happened while creating a container ?

1. process ถูกสร้างขึ้นมาใน host
2. process ถูกผูกเข้ากับ namespaces ของ resources เช่น pid, net และ uts
3. control group configuration ถูก apply ไปยัง process เพื่อ limit resources
4. security context ถูก apply ไปยัง process

### runC

ตัวอย่าง Commands ที่ใช้กับ runC มีดังนี้

| Commands | จุดประสงค์ |
|:--------:|:--------:|
| `runc run busybox` | run container ชื่อ busybox |
| `runc list` | list container ที่ run ด้วย runc |
| `runc ps busybox` | list process ที่ run ใน container ชื่อ busybox |
| `runc pause busybox` | หยุดการทำงานของ container ชื่อ busybox ชั่วขณะ |
| `runc resume busybox` | ยกเลิกการหยุดการทำงานของ container ชื่อ busybox ชั่วขณะ | 
| `runc state busybox` | ตรวจสอบสถานะของ container ชื่อ busybox |
| `runc delete -f busybox` | หยุดการทำงาน และ ลบ container ชื่อ busybox |

### Docker

ตัวอย่าง Commands ที่ใช้กับ docker มีดังนี้

| Commands | จุดประสงค์ |
|:--------:|:--------:|
| `docker container create -it alpine sh` | สร้าง container ชื่อ alpine ที่ run sh command ในแบบที่มี interactive terminal |
| `docker container start <container_id>` | start container |
| `docker container ls (-a)` | list container ที่ run อยู่ในขณะนั้น แต่ถ้าเพิ่ม -a จะ list container ทั้งหมด |
| `docker container run -it alpine sh` | สร้างและ run container ชื่อ alpine ที่ run sh แบบที่มี interactive terminal (เท่ากับ command ที่ 1 และ 2 รวมกัน)|
| `docker container inspect <container name/id>` | ตรวจสอบรายละเอียดของ container |
| `docker container stop` | หยุดการทำงานของ container |
| `docker container exec -it <container name/id>` | เข้าไปยัง container |
| `docker container rm (-f) <container name/id>` | ลบ container ถ้าใส่ -f จะ forece stop container ให้ด้วย |

Note
- ใช้ `ctrl-p+q` เพื่อออกจาก interactive terminal
- ตั้งแต่ Docker 1.13 มีการเปลี่ยนแปลง syntax ของ command นิดหน่อย โดยเพิ่ม sub-command "image" และ "container" ขึ้นมา เพื่อแยก command ในการจัดการ image และ container ให้ชัดเจนมากขึ้น

### rkt

ตัวอย่าง Commands ที่ใช้กับ rkt มีดังนี้

| Commands | จุดประสงค์ |
|:--------:|:--------:|
| `rkt run --interactive (--insecure-options=image) quay.io/coreos/alpine-sh` | run container ชื่อ alpine-sh แบบ interactive โดยถ้า image ไหนไม่ได้ sign GPG ให้ใส่ option `--insecure-options=image` ด้วย |
| `rkt --insecure-options=image run docker://nginx` | run container จาก dockerhub |
| `rkt list` | list container ที่ run ด้วย rkt |
| `rkt stop <UUID_of_Container>` | หยุดการทำงาน ของ container |
| `rkt rm <UUID_of_Container>` | ลบ container |
| `rkt status <UUID_of_Container>` | ตรวจสอบสถานะของ container |
| `systemd-run --slice=machine rkt run docker://nginx` | run container แบบ daemon mode โดยใช้ systemd เป็นคน manage ให้ |

Note
- ทุก native image ของ rkt ต้องมี GPG-signed เสมอ

<a name="chapter-8"></a>

---

# Chapter 8: Working with Image

### Working with Image

Image คือ ส่วนที่สำคัญที่สุดของ container ecosystem เพราะทุก container ต้อง run จาก image  

เราสามารถสร้าง container ได้จาก image ในทางกลับกันเราก็สามารถ แก้อะไรบ้างอย่างแล้ว convert container กลับไปเป็น image ได้ และโครงสร้างของ container จะเป็น layer ตามแบบ UnionFS

### Dockerhub

[Docker Hub](https://hub.docker.com/) เป็น repository ที่เก็บ image ที่สามารถทำ versioning ได้ด้วยการ tag นอกจากนั้นยังมี store สำหรับขาย enterprise-graded image ที่ผ่านการทำ security มาเรียบร้อย  เพื่อนำไปใช้ในงานที่ต้องการความปลอดภัยและ support

### Docker Image Operations

ตัวอย่าง Commands ที่ใช้ทำงานกับ docker image มีดังนี้

| Commands | จุดประสงค์ |
|:--------:|:--------:|
| docker search nginx | search image nginx ใน Docker Hub |
| docker image ls | list image ในเครื่องของเรา |
| docker image pull alpine | download image ชื่อ alpine จาก Docker Hub |
| docker image inspect alpine:lates | แสดงรายละเอียดของ image ชื่อ alpine ที่ tag ว่าเป็น version ล่าสุด |
| docker container run -itd alpine sh | start container alpine แล้ว run sh command ใน mode daemon (`-d`: daemon mode) |
| docker container diff myalpine | ตรวจสอบความแตกต่างของ container ที่ run อยู่ และ image ของมัน |
| docker container commit myalpine \<user\>/\<image_name\>:\<tag\> | สร้าง image ใหม่จาก container ชื่อ myalpine |
| docker container export <container name/id> > /path/to/file.tar | export container ออกมาเป็น tar file |
| cat /path/to/file.tar &#124; docker image import - \<user\>/\<image_name\>:\<tag\> | import tar file เข้ามาเป็น docker image |

Note
- default tag คือ `latest`
- image id ที่เป็นเลขฐาน 16 (hex) ได้มาจาการเข้ารหัส `SHA256`
- เราสามารถสร้าง หลายๆ container ได้จาก image อันเดียว

### Docker Image Push

ตัวอย่าง Commands ที่ใช้ push docker image เข้า repository มีดังนี้

| Commands | จุดประสงค์ |
|:--------:|:--------:|
| `docker login` | ทำการ authentication เพื่อ login เข้าไปยัง image repository |
| `docker info` | แสดงข้อมูลของ docker engine ที่ติดต่อด้วย |
| `docker image push <user>/<image_name>:<tag>` | push image ในเครื่องของเราไปยัง repository |
| ` docker image prune` | ลบ image ที่ไม่ได้ใช้ |
| `docker image rm (-f) nginx:alpine` | ลบ image nginx ที่มี tag ว่า alpine บนเครื่องเรา ถ้าใส่ option `-f` จะ force stop container ที่ใช้ image นี้ ก่อน remove image |


### rkt image

ตัวอย่าง Commands ที่ใช้ทำงานกับ rkt image มีดังนี้

| Commands | จุดประสงค์ |
|:--------:|:--------:|
| `rkt image list` | list rkt image บนเครื่องเรา |
| `rkt fetch coreos.com/etcd:v2.0.0` | download image etcd ที่มี tag v2.0.0 จาก coreos.com |
| `rkt fetch --insecure-options=image docker://nginx` | download image nginx ที่มี tag latest จาก Docker Hub

<a name="chapter-9"></a>

---

# Chapter 9: Working with Dockerfiles

### Dockerfiles

Dockerfiles เป็น file ที่เราเขียนวิธีการสร้าง image เอาไว้ เพื่อให้มันสามารถ create image ซ้ำได้ และทำได้อย่างอัตโนมัติ 

ด้วยความที่มันเป็น file ทำให้มันสามารถถูก share และ เก็บเป็นส่วนหนึ่งใน source code tree ใน source code repository อย่างเช่น github, gitlab หรือ bitbucket ได้ ทำให้เราสามารถ automate process ผ่าน CI/CD tools ได้ง่ายขึ้น

command ที่ใช้ในการ build docker image จาก dockerfiles คือ `docker build -t <tag>`

> จากบทที่ผ่านมา docker image มีลักษณะเป็น layer โดยแต่ละ layer จะคือ แต่ละ `RUN` instruction ใน Dockerfiles ดังนั้น ในการ build แต่ละ layer สามารถ cache ได้ถ้า layer นั้นๆ ไม่ได้เปลี่ยนแปลง ด้วยการ cache จะทำให้การ build image เร็วขึ้น (เราสามารถ disable การ cache และการใช้ cahce ระหว่างการ build ได้ด้วย option `--no-cache`)

### Dockerfiles Instructions

- List of instructions
  - `FROM`: set และ download base image สำหรับการ build image
  - `RUN`: execute command ตามที่ configure ไว้ โดยจะมีการ create layer ใหม่
  - `CMD`: กำหนด command default ในการ execute container มีได้แค่ 1 บรรทัด ถ้ามีหลายบรรทัด บรรทัดสุดท้ายเท่านั้นที่จะมีผล
  - `LABEL`: เป็น metadata ของ image ในรูปแบบ key-value pairs
  - `EXPOSE`: ให้ container listen port อะไร
  - `ENV`: set environment variables สำหรับ image
  - `ADD`: copy files หรือ directories จากภายนอกหรือจาก URL หรือ แตก tar file ไปยัง image
  - `COPY`: copy files หรือ directories จากภายนอกไปยัง image
  - `ENTRYPOINT`: เป็น command ที่ container จะ run ตอนที่มันถูก start
  - `VOLUME`: create volume เพื่อ mount เป็นอีก 1 mount point ใน image
  - `USER`: set user id และ group id ในการ run instruction `RUN`, `CMD` และ `ENTRYPOINT`
  - `WORKDIR`: set working directory สำหรับการ run instruction `RUN`, `CMD`, `ENTRYPOINT`, `COPY` และ `ADD`
  - `ARG`: กำหนด build arguments เพื่อรองรับ จาก docker build command
  - `ONBUILD`: บอกให้ คนที่เอา image นี้ไปทำ base image run command พวกนี้ด้วย
  - `STOPSIGNAL`: set system call signal ที่จะทำให้ container หยุดการทำงาน
  - `HEALTHCHECK`: กำหนด liveness และ readiness probe ในการตรวจสอบ  health ของ container
  - `SHELL`: กำหนด default shell เพื่อใช้กับ instruction `SHELL` default shell ของ Linux คือ `["/bin/sh", "-c"]` และของ Windows คือ `["cmd", "/S", "/C"]`

- Instruction format
  - **Shell form**: `<Instruction> <command>`
  - **Exec form**: `<Instruction> ["executable", "param1", "param2", ...]`

- Type of Instruction
  - **Build time**: `RUN`, `ADD`, `COPY`, `USER`, `WORKDIR`, `ONBUILD`, `SHELL`, etc
  - **Run time**: `CMD`, `ENTRYPOINT`

### CMD vs ENTRYPOINT

- `CMD`: ถ้าไม่มี `ENTRYPOINT` จะเป็น command ที่ container run โดยสามารถ overwrite ด้วยการ pass arguments ตอน start container
- `ENTRYPOINT`: เป็น command ที่ container run เหมือน `CMD` แต่ ไม่สามารถ overwrite ได้ และ ถ้ามีการระบุ `CMD` `CMD` จะเป็นหนึ่งใน arguments ของ `ENTRYPOINT`

<a name="chapter-10"></a>

---

# Chapter 10: Container Networking

### Docker Networking Command

- Network นี้จะเป็น network ที่ใช้ในการติดต่อสื่อสารระหว่าง container
- Default network ของ docker คือ bridge
- ใน mode bridge docker จะ สร้าง interface ขึ้นมาอีกอันหนึ่งคือ `docker0` บน interface แรกของเครื่อง ในที่นี้คือ `eth0` ในแต่ละ container ที่จะได้ veth ขึ้นมาเพื่อคุยกันภายใน namespace เดียวกัน ตามภาพ

|![Docker Networking](https://thepracticaldev.s3.amazonaws.com/i/px2xkqillyekot4bnm0j.png)|
|:--:|
|<center><sub><sup>Source: [docker-k8s-lab.readthedocs.io](https://docker-k8s-lab.readthedocs.io/en/latest/docker/bridged-network.html)</sup></sub></center>|

- สามารถดูสถานะของ bridge ได้จาก host โดยใช้ command `bectl show`

### Docker Networking Drivers

- Docker Network Drivers มีดังนี้
  - `bridge`: เป็น default network driver ถ้าไม่ระบุอะไรจะเป็น bridge ทั้งหมด เป็น Standalone mode
  - `host`: เป็น driver ที่ share network namespaces เดียวกับ host OS ปกติจะเป็น Standalone mode แต่สามารถ set up เป็น Swarm ได้ใน Docker 17.06 ขึ้นไป
  - `overlay`: ใช้ connect docker daemon หลายๆ ตัวเข้าด้วยกันเป็น swarm หรือ แค่อยากจะให้ docker daemon ที่อยู่ต่างเครื่องกันคุยกันได้ driver ตัวนี้ไม่ได้อาศัย routing ระดับ OS เพื่อให้มันคุยกันได้
  - `macvlan`: เราสามารถ assign MAC Address ไปยัง container ได้เลย ดังนั้นจึงเป็นเหมือนการ assign physical network ให้ container เลย เหมาะกับ legacy application ต้องการ physical interface
  - `none`: ไม่ assign interface ให้ container เลย (disable container networking) ดังนั้นใน container จะเห็นแค่ localhost interface

- ตัวอย่าง Commands ที่ใช้ในการจัดการ container networking

| Commands | จุดประสงค์ |
|:--------:|:--------:|
| `docker network ls` | list network ทั้งหมด |
| `docker network inspect <network_id>` | ตัวสอบรายละเอียดของ network |
| `docker container run -it --network=none alpine sh` | run container โดยระบุ driver ของ container เป็น none นั้นคือ isable container networking |
| `docker container run -it --network=container:web alpine sh` | run container ให้มี network เดียวกับ container ชื่อ web โดย ทั้ง 2 containers จะมี IP เดียวกันเลย |
| `docker network create mynet` | create network ชื่อ mynet แต่เราไม่ได้ ระบุ driver ดังนั้น จะเป็น `bridge` |

### Container Networking Standard Specifications

Docker มีหลักๆ 2 Specifications ดังนี้
- **Container Network Model (CNM)**:
   - ริเริ่มโดย Docker
   - ใช้ `libnetwork`
   - มี build-in network driver
   - support 3<sup>rd</sup> party driver เช่น Cisco, Calico, Kuryr, Weave, etc

|![CNM](https://thepracticaldev.s3.amazonaws.com/i/8zyez54dfxzxog9dm7az.png)|
|:--:|
|<center><sub><sup>Source: [thenewstack.io](https://thenewstack.io/container-networking-landscape-cni-coreos-cnm-docker/)</sup></sub></center>|

- **Container Network Interface (CNI)**:
   - ริเริ่มโดย CoreOS
   - สามารถทำงานได้ใน container runtime ที่ต่างกัน
   - สามารถใช้ได้กับ Container Orchestrator เช่น Kubernetes, Mesos และ Cloud Foundry
   - support 3<sup>rd</sup> party driver เช่น Weave, Calico, etc
   - หลักการทำงานคือ container runtime จะ create namespace และ ให้ driver เป็นคน assign IP มาให้ container runtime อีกที

|![CNI](https://thepracticaldev.s3.amazonaws.com/i/igukxqkyohydjrcl1vfr.png)|
|:--:|
|<center><sub><sup>Source: [thenewstack.io](https://thenewstack.io/container-networking-landscape-cni-coreos-cnm-docker/)</sup></sub></center>|

### rkt Networking

rkt support 3 modes
- `none`: ไม่ assign interface ให้ container เลย โดย ใน container จะเห็นแค่ localhost interface

|![none](https://thepracticaldev.s3.amazonaws.com/i/l9z2vepacb62s2c4ejsl.png)|
|:--:|
|<center><sub><sup>Source: [CoreOS.com](https://coreos.com/blog/rkt-cni-networking.html)</sup></sub></center>|

- `host`: container จะ share namespace กับ host

|![host](https://thepracticaldev.s3.amazonaws.com/i/yyi7qqyi7pzujxjlr3z9.png)|
|:--:|
|<center><sub><sup>Source: [CoreOS.com](https://coreos.com/blog/rkt-cni-networking.html)</sup></sub></center>|

- `container` (default): 
  - ใช้ PTP Plugin ซึ่งจะสร้าง veth pair ระหว่าง host และ pod
  - ใช้ technic **NAT** และ **Routing** ในการติดต่อกับ external network
  - build-in network ประกอบด้วย 
    - `ptp`: จะสร้าง veth pair ระหว่าง pod และ host interface
    - `bridge`: จะสร้าง veth pair ระหว่าง pod และ bridge interface
    - `macvlan`: เป็นการสร้าง virtual interface ขึ้นมาบน physical master interface จากนั้น assign random MAC address เข้าไป
    - `ipvlan`: เป็นการสร้าง virtual interface ขึ้นมาบน physical master interface แต่ไม่ assign random MAC address เข้าไป แต่สามารถมี IP ที่ต่างไปจาก physical master interface

|![container](https://thepracticaldev.s3.amazonaws.com/i/yk6o8byuqdxwb7ixd6u4.png)|
|:--:|
|<center><sub><sup>Source: [CoreOS.com](https://coreos.com/blog/rkt-cni-networking.html)</sup></sub></center>|

- ตัวอย่าง Commands ที่ใช้ในการจัดการ container networking

| Commands | จุดประสงค์ |
|:--------:|:--------:|
| `rkt run --interactive --net=host quay.io/coreos/alpine-sh:latest` | run rkt container โดยระบุ interface driver เป็นแบบ host|
| `rkt run --port=80-tcp:8000 --insecure-options=image docker://nginx` | run rkt container โดย map port 8000 ของ host กับ port 80 ของ container |

- เราสามารถ create a user-defined bridge network ได้ดังนี้

``` bash
# cat /etc/rkt/net.d/10-containers.conf
{
  "name": "containers",
    "type": "bridge",
    "bridge": "rkt1",
    "ipam": {
      "type": "host-local",
      "subnet": "10.1.0.0/16"
    }
}
```
<a name="chapter-11"></a>

---

# Chapter 11: Container Storage

### UnionFS with Copy-on-Write

UnionFS เป็นการรวมเอา file system อื่นๆ มารวมกันเป็นเสมือน file system เดียว ให้จิตนาการเหมือนจานแก้วที่วางซ้อนกันเมื่อนเรามองจากด้านบนจะเห็นเหมือนเป็นจานจานเดียว

|![UnionFS](https://thepracticaldev.s3.amazonaws.com/i/t586s7rofvo9zbihhm5a.jpg)|
|:--:|
|<center><sub><sup>Source: [Linux Foundation](https://www.linuxfoundation.org/)</sup></sub></center>|

เมื่อ run container ขึ้นมา ephemeral storage จะถูกสร้างขึ้นมาด้วย unionFS ถ้ามองจาก ephemeral storage ของ container จะเห็นทั้ง file f1-f5

- `file f1` อยู่ในแต่ ephemeral storage ดังนั้นเมื่อ container ถูกลบไป file f1 จะหายไป
- `file f4` เมื่อถูกลบมันจะแค่ถูก mark เป็นสถานะ "white-out" ใน container แต่ file f4 ยังอยู่ใน base image layer
- `file f5` เมื่อถูกเปลี่ยนแปลงแก้ไขมันจะ effect แค่ใน ephemeral storage layer เท่านั้น (Copy-on-Write)

### Managing Storage with Docker

- สำหรับ ephemeral storage, Docker สามารถใช้ UnionFS with CoW กับ storage driver เหล่านี้ได้
  - **Linux**: `aufs`, `btrfs`, `zfs`, `overlay`, `overlay2`, `devicemapper` และ `vfs`
  - **Windows**: `filter driver`
- สำหรับ persistent storage, Docker ใช้ feature `volume` ซึ่งมีทั้ง
  - `Volumes` เป็นการ create docker volume ขึ้นมาให้ mount
  - `Bind mounts` คือการ mount directory ของ host OS มาใช้ใน container
  - `tmpfs mounts` คือการ mount non-persistent storage
  - `named pipes` ใช้ในการ run 3<sup>rd</sup>Party plugin เช่น Flocker, GlusterFS, REX-Ray, gce-docker, HPE 3Par Volume เป็นต้น [เพิ่มเติม](https://docs.docker.com/engine/extend/legacy_plugins/)


- ตัวอย่าง Commands ที่ใช้ในการจัดการ container storage

| Commands | จุดประสงค์ |
|:--------:|:--------:|
| `docker volume create my-vol` | สร้าง container volume ขึ้นมา (defalt เป็น local) |
| `docker volume ls` | list volume ในระบบ |
| `docker volume inspect my-vol` | แสดงรายละเอียดของ volume |
| `docker volume rm my-vol` | ลบ container volume |
| `docker run -d --name devtest --mount source=myvol2,target=/app nginx:latest` | run container โดยใช้ `--mount` ในการ mount volume "myvol2" กับ  path /app ใน container |
| `docker run -d --name=nginxtest -v nginx-vol:/usr/share/nginx/html:ro nginx:latest` | run container โดยใช้ `-v` ในการ mount volume "nginx-vol" กับ  path /usr/share/nginx/html ใน container แบบ read-only mode |

**Note**
- ถ้าลบ container ไปแล้ว volume จะยังคงอยู่
- ถ้าจะลบ container พร้อมกับ volume ให้เพิ่ม option `-v` ให้กับ `docker conatiner rm` command

### rkt Volumes

- rkt support volume 2 ชนิด คือ
  - **Host Volumes** (`host`): mount directory ของ host OS มาใช้ใน container (`--volume NAME,kind=host,source=SOURCE_PATH,readOnly=BOOL`)
  - **Empty Volumes** (`empty`): เป็นการ mount local storage ซึ่งจะถูกทำลายเมื่อ container ถูกลบ (`--volume NAME,kind=empty,mode=MODE,uid=UID,gid=GID`)

- ตัวอย่าง Commands ที่ใช้ในการจัดการ container storage

| Commands | จุดประสงค์ |
|:--------:|:--------:|
| `rkt run --volume data,kind=host,source=/srv/data,readOnly=false example.com/app1` | run container โดย mount host volume path /srv/data กับ container path /var/data แบบ read-write |
| `rkt run --volume data,kind=empty,readOnly=false example.com/app1` | run container โดย mount empty volume กับ container path /var/data แบบ read-write |

<a name="chapter-12"></a>

---

# Chapter 12: Build a Multi-Container Application with Docker-Compose

### Name Solving Technique

- **1<sup>st</sup> generation technique**: ใช้ options `--link` ในการเชื่อมต่อระหว่าง container โดย Docker daemon จะเข้าไป inject alias record ใน `/etc/hosts` ของ containers
- **2<sup>nd</sup> generation technique**: ใช้ dns-like feature ที่มาพร้อมกับ Docker engine

### Docker Compose

`docker-compose` เป็น command ที่อ่าน Compose file ใน YAML format แล้วทำการ start container ขึ้นมาตามที่เราระบุไว้ใน Compose file นั้น

โดยในแต่ละครั้งที่ run Compose file `docker-compose` จะสร้าง custome network เพื่อใช้กับ containers ที่ระบุไว้ใน Compose file นั้นๆ

- ตัวอย่าง Compose file 

``` yaml
version: "3.7"

services:
  wordpress:
    image: wordpress
    ports:
      - "8080:80"
    networks:
      - overlay
    deploy:
      mode: replicated
      replicas: 2
      endpoint_mode: vip

  mysql:
    image: mysql
    volumes:
       - db-data:/var/lib/mysql/data
    networks:
       - overlay
    deploy:
      mode: replicated
      replicas: 2
      endpoint_mode: dnsrr

volumes:
  db-data:

networks:
  overlay:
```

- ตัวอย่าง Commands ที่ใช้ในการจัดการ container storage

| Commands | จุดประสงค์ |
|:--------:|:--------:|
| `docker-compose up` | start containers และ configure ต่างๆ ใน Compose file ใน foreground mode |
| `docker-compose up -d` | start containers และ configure ต่างๆ ใน Compose file ใน background mode |
| `docker-compose ps` | list containers ที่ create ไป |
| `docker-compose stop` | stop containers ที่ create ไป |
| `docker-compose rm` | ลบ containers ที่ create ไป |

<a name="chapter-13"></a>

---

# Chapter 13: Introducing Docker APIs

### Docker Remote APIs

> เนื่องจาก docker return ค่าเป็น minified json ดังนั้น command ที่สำคัญต้องลงเพิ่มเพื่อทำให้ เป็น pretty json คือ `jq`

โดย default Docker daemon bind UNIX socket ดังนั้นเราสามารถ `curl` UNIX socket ได้ดังนี้

- `curl --unix-socket /var/run/docker.sock http:/info | jq`
- `curl --unix-socket /var/run/docker.sock http:/images/json | jq`
- `curl --unix-socket /var/run/docker.sock http:/containers/json | jq`

### Docker client library

- Official SDK จะมี 2 ภาษาคือ `Go` และ `Python` แต่ก็ยังมี Unofficial SDK ภาษาอื่นๆ อีก [อ่านเพิ่มเติม](https://docs.docker.com/develop/sdk/)
- ตัวอย่าง การใช้งาน Python SDK
  - Install library: `pip install docker`
  - Coding

``` py
# import library
import docker
# connect to docker daemon 
client = docker.DockerClient(base_url='unix://var/run/docker.sock') 
# run containers
client.containers.run("ubuntu:latest", "echo hello world")
# list containers
client.containers.list()
# list image
client.images.list()
```

<a name="chapter-14"></a>

---

# Chapter 14: Docker Tips and Tricks

### Tips and Tricks

- Docker มี auto completion นะใช้ซะ ชีวิตจะดีขึ้น
- สามารถใช้ GO templates ในการเลือก output จาก docker inspect ได้

``` bash
# docker container inspect --format '{{.NetworkSettings.IPAddress}}' <container_id/name>
172.17.0.6
```

- filters with Docker sub-commands

``` bash
# docker container ls -a --filter 'exited=0'
CONTAINER ID IMAGE        COMMAND                CREATED         STATUS                     PORTS       NAMES
bd0936a70412 nginx:alpine "nginx -g 'daemon off" 7 minutes ago   Exited (0) 47 seconds ago              determined_dijkstra
3397ab1b421b alpine       "sh"                   8 minutes ago   Exited (0) 7 minutes ago               awesome_galileo
```

- ดูการใช้งาน disk space สำหรับแต่ละ container

``` bash
# docker system df -v
Images space usage:

REPOSITORY      TAG             IMAGE ID        CREATED
SIZE            SHARED SIZE     UNIQUE SIZE     CONTAINERS
nginx           latest          cc1b61406712    13 days ago
181.8 MB        0 B             181.8 MB        1
alpine          latest          88e169ea8f46    5 weeks ago
3.984 MB        0 B             3.984 MB        2

Containers space usage:
CONTAINER ID    IMAGE           COMMAND                 LOCAL VOLUMES
SIZE            CREATED         STATUS                  NAMES
f3686500c54c    alpine          "sh"                    1
86 B            6 minutes ago   Up 6 minutes            cmyvol
3bb26bfde13d    alpine          "sh"                    1
22 B            20 minutes ago  Up 20 minutes           cvol
d35e448942a7    nginx           "nginx -g 'daemon ..."  0
0 B             54 minutes ago  Created                 web1

Local Volumes space usage:
VOLUME NAME                                                         LINKS   SIZE
c2842864d4fe791d8a9615a3724f684503d554b61fbbfc58b4c91a45f1afa03f    1       0 B
myvol                                                               1       37 B
```

- ลบ docker image ที่ไม่ได้ใช้เพื่อ clean up host disk usage

``` bash
docker system prune -a
```