Docker

Docker 是一個開放原始碼軟體,是一個開放平台,用於開發應用、交付應用、執行應用。 Docker允許用戶將基礎設施中的應用單獨分割出來,形成更小的顆粒,從而提高交付軟體地速度。 Docker容器 與虛擬機器類似,但原理上,容器是將作業系統層虛擬化,虛擬機器則是虛擬化硬體,因此容器更具有可攜式性、高效地利用伺服器。

Secure Docker Network

With iptables

對於 Container 不同啟動方式,在 iptables 的腳本設計會有些不同。

for docker-compose)

#!/usr/bin/env bash

# Purpose: Restrict the access to the container from external IPs.
# Author: Alang, 2019/8/14
#

CONTAINER="nginx_mysql_web_1"
EXT_IF="eth0"
BRG_IF="$(ip a | awk '/br-[a-z0-9]+:/{print $0}' | awk '{print $2}' | sed 's/://g')"

if [ ! -x /usr/bin/docker ]; then
    echo "Abort: Unable to run the docker command!"
    echo "Recommendation: Check if the Docker was installed."
    exit 1
fi

if [ -z "$BRG_IF" ]; then
    echo "Abort: The network interface (br-????) with container not found. "
    exit 1
fi

# Check the network interfaces
for i in $EXT_IF $BRG_IF;do
    if ! (ip a show $i >/dev/null 2>&1); then
        echo "Abort: The network interface $i doesn't exist"
        exit 1
    fi
done

# Check if the DOCKER-USER chain exists, if it does flushing the rules.
iptables -C FORWARD -j DOCKER-USER 2> /dev/null

if [ $? -eq 0 ]; then
    # Flush all existing rules
    iptables -F DOCKER-USER
else
    echo "Abort: The chain DOCKER-USER not found!"
    echo "Recommendation: "
    echo "  - Is the Docker daemon running?"
    echo "  - Is the version of Docker 17.06+?"
    exit 1
fi

## Docker container named www-nginx public access policy
## If using docker-compose, change FORMAT to
## --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'
##
#WWW_IP="$(/usr/bin/docker inspect --format {{.NetworkSettings.IPAddress}} $CONTAINER 2>/dev/null)"
WWW_IP="$(/usr/bin/docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $CONTAINER 2>/dev/null)"

if [ -z "$WWW_IP" ];then
    echo "Abort: The docker IP isn't detected!"
    echo "Recommendation:"
    echo "  - Is the specified container UP?"
    echo "  - Adjsut the variable \$WWW_IP."
    exit 1
fi

# Default action
iptables -I DOCKER-USER -i $EXT_IF -j DROP   

## Insert web server container filter rules  
## Allow All IPs to access 80 & 443
#iptables -I DOCKER-USER -i $EXT_IF -p tcp -d $WWW_IP --dport 80  -j ACCEPT
#iptables -I DOCKER-USER -i $EXT_IF -p tcp -d $WWW_IP --dport 443 -j ACCEPT
## Allow Cloudflare IPs to acces 443
## Check the IP list of Cloudflare at the URL https://www.cloudflare.com/ips-v4
iptables -I DOCKER-USER -o $BRG_IF -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -I DOCKER-USER -i $BRG_IF ! -o $BRG_IF -j ACCEPT
iptables -I DOCKER-USER -m state --state RELATED -j ACCEPT
iptables -I DOCKER-USER -i $BRG_IF -o $BRG_IF -j ACCEPT

iptables -I DOCKER-USER -i $EXT_IF -p tcp  -d $WWW_IP -s 173.245.48.0/20  --dport 443 -j ACCEPT
iptables -I DOCKER-USER -i $EXT_IF -p tcp  -d $WWW_IP -s 103.21.244.0/22  --dport 443 -j ACCEPT
iptables -I DOCKER-USER -i $EXT_IF -p tcp  -d $WWW_IP -s 103.22.200.0/22  --dport 443 -j ACCEPT
iptables -I DOCKER-USER -i $EXT_IF -p tcp  -d $WWW_IP -s 103.31.4.0/22    --dport 443 -j ACCEPT
iptables -I DOCKER-USER -i $EXT_IF -p tcp  -d $WWW_IP -s 141.101.64.0/18  --dport 443 -j ACCEPT
iptables -I DOCKER-USER -i $EXT_IF -p tcp  -d $WWW_IP -s 108.162.192.0/18 --dport 443 -j ACCEPT
iptables -I DOCKER-USER -i $EXT_IF -p tcp  -d $WWW_IP -s 190.93.240.0/20  --dport 443 -j ACCEPT
iptables -I DOCKER-USER -i $EXT_IF -p tcp  -d $WWW_IP -s 188.114.96.0/20  --dport 443 -j ACCEPT
iptables -I DOCKER-USER -i $EXT_IF -p tcp  -d $WWW_IP -s 197.234.240.0/22 --dport 443 -j ACCEPT
iptables -I DOCKER-USER -i $EXT_IF -p tcp  -d $WWW_IP -s 198.41.128.0/17  --dport 443 -j ACCEPT
iptables -I DOCKER-USER -i $EXT_IF -p tcp  -d $WWW_IP -s 162.158.0.0/15   --dport 443 -j ACCEPT
iptables -I DOCKER-USER -i $EXT_IF -p tcp  -d $WWW_IP -s 104.16.0.0/12    --dport 443 -j ACCEPT
iptables -I DOCKER-USER -i $EXT_IF -p tcp  -d $WWW_IP -s 172.64.0.0/13    --dport 443 -j ACCEPT
iptables -I DOCKER-USER -i $EXT_IF -p tcp  -d $WWW_IP -s 131.0.72.0/22    --dport 443 -j ACCEPT

# My IPs
#iptables -I DOCKER-USER -i $EXT_IF -p tcp  -d $WWW_IP -s 219.70.88.133    --dport 443 -j ACCEPT


[ $? -eq 0 ] && echo "Done."
## EOL

for docker)

#!/usr/bin/env bash

# Usage:
# timeout 10 docker_iptables.sh
#
# Use the builtin shell timeout utility to prevent infinite loop (see below)

CONTAINER="raida17"

if [ ! -x /usr/bin/docker ]; then
    exit
fi

# Check if the PRE_DOCKER chain exists, if it does there's an existing reference to it.
iptables -C FORWARD -o docker0 -j PRE_DOCKER 2> /dev/null

if [ $? -eq 0 ]; then
    # Remove reference (will be re-added again later in this script)
    iptables -D FORWARD -o docker0 -j PRE_DOCKER
    # Flush all existing rules
    iptables -F PRE_DOCKER
else
    # Create the PRE_DOCKER chain
    iptables -N PRE_DOCKER
fi

# Default action
iptables -I PRE_DOCKER -j DROP

# Docker Containers Public Admin access (insert your IPs here)
#iptables -I PRE_DOCKER -i eth0 -s 192.184.41.144 -j ACCEPT
#iptables -I PRE_DOCKER -i eth0 -s 120.29.76.14 -j ACCEPT

# Docker Containers Restricted LAN Access (insert your LAN IP range or multiple IPs here)
#iptables -I PRE_DOCKER -i eth1 -s 192.168.1.101 -j ACCEPT
#iptables -I PRE_DOCKER -i eth1 -s 192.168.1.102 -j ACCEPT

# Docker internal use
iptables -I PRE_DOCKER -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -I PRE_DOCKER -i docker0 ! -o docker0 -j ACCEPT
iptables -I PRE_DOCKER -m state --state RELATED -j ACCEPT
iptables -I PRE_DOCKER -i docker0 -o docker0 -j ACCEPT

# Docker container named www-nginx public access policy
# If using docker-compose, change format to
# --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'
WWW_IP_CMD="/usr/bin/docker inspect --format {{.NetworkSettings.IPAddress}} $CONTAINER"
WWW_IP=$($WWW_IP_CMD)

# Double check, wait for docker socket (upstart docker.conf already does this)
while [ ! -e "/var/run/docker.sock" ]; do echo "Waiting for /var/run/docker.sock..."; sleep 1; done

# Wait for docker web server container IP
while [ -z "$WWW_IP" ]; do echo "Waiting for $CONTAINER IP..."; WWW_IP=$($WWW_IP_CMD); done

## Insert web server container filter rules
## Allow All IPs to access 80 & 443
#iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP --dport 80  -j ACCEPT
#iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP --dport 443 -j ACCEPT
## Allow Cloudflare IPs to acces 443
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP -s 103.21.244.0/22 --dport 443 -j ACCEPT
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP -s 103.22.200.0/22 --dport 443 -j ACCEPT
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP -s 103.31.4.0/22 --dport 443 -j ACCEPT
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP -s 104.16.0.0/12 --dport 443 -j ACCEPT
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP -s 108.162.192.0/18 --dport 443 -j ACCEPT
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP -s 131.0.72.0/22 --dport 443 -j ACCEPT
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP -s 141.101.64.0/18 --dport 443 -j ACCEPT
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP -s 162.158.0.0/15 --dport 443 -j ACCEPT
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP -s 172.64.0.0/13 --dport 443 -j ACCEPT
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP -s 173.245.48.0/20 --dport 443 -j ACCEPT
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP -s 188.114.96.0/20 --dport 443 -j ACCEPT
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP -s 190.93.240.0/20 --dport 443 -j ACCEPT
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP -s 197.234.240.0/22 --dport 443 -j ACCEPT
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP -s 198.41.128.0/17 --dport 443 -j ACCEPT
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP -s 199.27.128.0/21 --dport 443 -j ACCEPT


# Finally insert the PRE_DOCKER table before the DOCKER table in the FORWARD chain.
iptables -I FORWARD -o docker0 -j PRE_DOCKER
With ufw

 

MySQL Backup

從 Host 執行

backup-db.sh

#!/bin/bash

BKDIR="db_backups"
BKFILE="cloudcoin_raida#17.`date +%y%m%d`.sql"
DBUSER="ThisDBUser"
DBNAME="cloudcoin_raida"
CONTNAME="raida17"
CONTDIR="/opt/data/$BKDIR"
HOSTDIR="/docker_vol/raida/data/$BKDIR"
KEEP=2


[ -d $HOSTDIR ] || mkdir $HOSTDIR
cd $HOSTDIR
start_s=$(date +%s)
echo "*********** START `date "+%F@%T"` **************"

# Full Backup
echo "-> Back up Full DB "
docker exec $CONTNAME bash -c "mysqldump --single-transaction --add-drop-table -u $DBUSER  $DBNAME > $CONTDIR/$BKFILE"
retval=$?

if [ $retval -eq 0 ];then
    echo "... [success]"
else
    echo "... [failure]**"
    exit 1
fi

# Compress the files
echo "-> Compress the dump file "
gzip -f $HOSTDIR/$BKFILE

retval=$?
if [ $retval -eq 0 ];then
    echo "... [success]"
    BKFILE="$BKFILE.gz"
else
    echo "... [*failure*]"
    exit 1
fi

# Purge the old files
echo "-> Purge the old files"
ls_files=($(ls -lt cloudcoin_raida* | awk -F ' ' '{print $9}'))
len=${#ls_files[@]}
i=$KEEP
while (($i < $len));do
        rm -vf "${ls_files[$i]}"
        let i++
done

end_s=$(date +%s)
elapsed=$(( end_s - start_s  ))
echo 
echo "Elapsed: $elapsed seconds"
echo "Output File: $HOSTDIR/$BKFILE"
echo "*********** END `date "+%F@%T"` **************"
echo

 

基本指令操作

線上求助
man docker <command>
man docker build
man docker rmi 
管理 Images
## 搜尋 Docker Hub 上的 image name
docker search lamp

## 顯示已下載所有 image name
docker images

## 檢視既有 image 的詳細資訊
docker inspect <image-name>

## 網路下載 image
docker pull ubuntu:13.10

## 刪除已下載的 image
docker rmi <image-name>

## 刪除所有 images
docker rmi $(docker images -q)

## 刪除所有 images,除了 my-images 以外
docker rmi $(docker images | grep -v 'ubuntu\|my-image' | awk {'print $3'})

## 刪除所有 <none> 的有問題 images
docker rmi $(docker images -f "dangling=true" -q)

## 刪除與 myapp/myimage 相關的 <none> 的 images
docker rmi $(docker images myapp/myimage -f "dangling=true" -q)

## 列出所有 images 之間繼承的關係
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock nate/dockviz images -t
管理 Containers
## 開啟並進入 container 的 console
docker run -it <image-name> /bin/bash
docker run -it --name <container-name> <image-name> /bin/bash

## 以 daemon 方式啟動 container
docker run -d -p 11180:80 <image-name>
docker run -d --name web <image-name>
TIP: 啟動 container 時可以自訂名稱以方便管理

docker run -d -p 80:80 --rm <image-name>
加上 --rm 時,當停止 container 時,會自動被刪除(與 docker rm 指令相同),且無法使用啟動指令
(docker start) 只能使用 docker run 啟動。 

## 檢查目前已經啟動的 containers
docker ps
docker ps -a

## 檢視開啟中 container 的詳細資訊,包含 Volumes、IP、Hostname 等等
docker inspect <container-id>

## 刪除指定的 container
docker rm <container-id> 

## 刪除所有的 containers
NOTE: 小心,這也會刪除正在執行的 container
docker ps -a -q | xargs -n 1 docker rm
docker rm $(docker ps -aq) 

## 刪除所有已經終止的 container
docker ps -a | grep "Exited" | awk '{print $1}' | xargs docker rm
NOTE: 這常用於在重新啟動 container 或 rebuild image 時遇到錯誤訊息的解決方法。

## 停止 container
docker stop <container-id>

## Stop all containers
docker stop $(docker ps -aq)
docker ps -aq | xargs docker stop

## 匯出 container
docker export <container-id>  > ubuntu-mysql.tar 

## 匯入 container
cat ubuntu-mysql.tar | docker import - <image-name>

## 跳離目前開啟中的 container
按下 Ctrl P 後再按 Ctrl Q
NOTE: 如果無法成功跳離,原因可能是 Ctrl+P 是 Bash 內定的快捷鍵(回到前一個指令)

## 重新進入開啟中的 container
docker attach <container-id>
或
docker attach <container-name>
如果 container 是以 daemon 啟動,改用以下方式
docker exec -it <container-id/name> /bin/bash

## 儲存開啟中 container 內容
docker commit <container-id> <image-name> 

## 顯示指定 container 的 IP
docker inspect <container-id> | grep IPAddress | cut -d '"' -f 4 
複製/搬移 container 至另一部主機
## Stop the container
docker stop <container-name>

## Save container image
docker commit <container-name> mycontainerimage
docker save mycontainerimage | gzip > mycontainerimage.tar.gz

## Load container image to destination host
gunzip -c mycontainerimage.tar.gz | docker load 

## Transfer image without creating a file
docker save mycontainerimage | gzip | ssh root@203.0.113.1 'gunzip | docker load'

TIP:
執行 exit 可以離開目前的 container,回到原先的 Linux

一旦離開 container,所有之前做過的變更,將全部失效,如果要保留做過的變更,必須使用 commit 產生一個新的 image。

-p 將 Host 的 port 11180 轉送至 container 的 port 80

管理 Volumes

Docker 的 Data Volume 是一個很特別的目錄設計,主要用在不同 containers 之間的資料分享,永久保存資料等。

主要特點:

// 啟用 volume
docker run -t -i -p 80:80 -v ${PWD}/webapp:/webapp alang/centos5-lamp_php51

TIP:
格式:-v <host-dir>:<container-dir>

在 container 內會自動新增一個目錄名為 /webapp,儲存到這個目錄的所有資料都會被保留。

被保留的資料會儲存到 host 的某個特定目錄內,即使 container 被移除,這些資料還是會存在,要如何找到這個特定目錄:

docker inspect -f {{.Volumes}} <container-id>

一般預設會是
/var/lib/docker/vfs/dir/bfebd8cb6......

 

Docker Network
# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                      NAMES
7ccaf6119fa8        nginx:latest        "nginx -g 'daemon of…"   2 days ago          Up 39 hours         0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp   nginx_mysql_web_1
81a920bb51a6        nginx_mysql_php     "docker-php-entrypoi…"   2 days ago          Up 2 days           9000/tcp                                   nginx_mysql_php_1
437a7501198f        mariadb:10.3        "docker-entrypoint.s…"   2 days ago          Up 2 days           3306/tcp                                   nginx_mysql_db_1

# docker network ls
NETWORK ID          NAME                  DRIVER              SCOPE
852eff02220e        bridge                bridge              local
334d2b8571a4        host                  host                local
b97cae66a977        nginx_mysql_default   bridge              local
40d15afb34b4        none                  null                local

# brctl show
bridge name     bridge id               STP enabled     interfaces
br-b97cae66a977         8000.0242569e79ff       no              veth3ce8cbd
                                                        veth5129652
                                                        veth55dcdf7
docker0         8000.0242faff70bb       no

取得 container IP

#> docker inspect <container_id> | grep -i ipaddr
#> docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' <container_id>
管理 Docker

檢查版本資訊

// 檢查 Docker 版本
docker version

// Docker 更多資訊
docker info

host 與 container 間交換檔案

#> docker cp <container-name>:/etc/nginx/nginx.conf /data/web/conf
#> docker cp host_source_path my_container:destination_path
#> docker cp -a host_source_path my_container:destination_path

清除沒用的物件

這會清除所有已停止的 container,沒有在用的docker層網路介面與 <none> 不完整的 image。
#> docker system prune

上述指令會保留 volume 裡的資料,如果要一併清除,須加上 --volumes
#> docker system prune -a --volumes

Docker Compose 指令

NOTE: 服務一旦佈署完成,docker-compose.yml 的路徑如果有變更,就不能繼續使用指令 docker-compose 來管理 container,不過已經啟動的服務運行不會影響,但關閉後就無法再被啟動。

新增與啟動所有應用服務

docker-compose up -d
docker-compose up -d <service-name>

Build the image of the service

docker-compose build <service-name>
docker-compose up --rebuild <service-name>

目前的應用服務狀態

docker-compose ps

停止/啟動所有應用服務但不要結束所屬 container

docker-compose stop
docker-compose start 

如果 container 屬性沒有異動,只是所屬的服務需要重啟載入新設定,可以使用這。

停止所有應用服務並結束所屬 container 執行

docker-compose down

docker-compose stop <service-name>
docker-compose rm -f <service-name>

NOTE:

- 如果有修改 docker-compose.yml,必須使用 down 關閉服務後,用 up -d 重新啟動。

- 停止服務後,重新啟動只能使用 docker-compose up -d

檢視服務啟動失敗的日誌

docker-compose logs

執行某個應用服務的特定程序

docker-compose exec <service-name> sh -c "pwd"

Installation

Docker Compose

The latest download: https://docs.docker.com/compose/install/

sudo curl -L "https://github.com/docker/compose/releases/download/1.26.2/docker-compose-$(uname -s)-$(uname -m)" \
-o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

# If you can't run the command, creating a symblic link as follows.
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

docker-compose --version
Docker

Latest: https://docs.docker.com/engine/install/

Debian 9)

$ sudo apt-get update

$ sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    software-properties-common

# Add Docker's GPG key
$ curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -

# Set up the repository
$ sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/debian \
   $(lsb_release -cs) \
   stable"

# Install Docker Engine
$ sudo apt-get update
$ sudo apt-get install docker-ce docker-ce-cli containerd.io

# Verify that Docker is installed correctly.
$ sudo docker run hello-world

 

Dockerfile

Reference