在樹莓派上學習 Docker —— Part 2

Docker 的特色或者說使用方法就所有包含在官網的首頁中了——Build,Ship,and Run Any App,Anywhere,能夠簡單的分爲三個部分:Build、Ship、Run。php

注意:若是沒有添加當前用戶到 docker 用戶組,那麼全部 docker 命令都須要增長 sudo 才能執行。html

Pull

運行容器前須要有鏡像,鏡像能夠在本地 Build 也能夠經過鏡像倉庫獲取(pull)。linux

咱們能夠在 Docker Store 上找到不少現成的鏡像,不過須要注意的是因爲 Docker 不是虛擬機,因此只能運行相同平臺的鏡像。樹莓派安裝的 Raspbian 屬於 armhf 架構,所以只能運行 armhf 的 Docker 鏡像。git

說明: 這裏的說法並不許確,考慮到有 QEMU 的存在實際上 Linux 是能夠跨平臺運行應用的。 簡單來講,只要有運行環境,X86_64系統也能運行 arm32v7 應用,詳細資料請查看 QEMU 官方文檔github

另外須要注意的是,Docker Store 上的鏡像分爲官方(Official)和社區(Community)兩種,官方鏡像更有安全保障,可是社區鏡像更加豐富。redis

打開 Docker Store查看鏡像 armhf/hello-world 的信息 。能夠看到 armhf/hello-world 鏡像已是廢棄狀態,並推薦了更加準確的鏡像新地址:sql

DEPRECATED
The armhf organization is deprecated in favor of the more-specific arm32v7 and arm32v6 organizations, as per https://github.com/docker-library/official-images#architectures-other-than-amd64. Please adjust your usages accordingly.docker

樹莓派3 使用的 SoC 是 Broadcom BCM2837 64bit ARMv8,但 Raspbian 是 32位系統,所以 arm32v7 和 arm32v6 的鏡像應該都能使用。首先找一個 arm32v7 與 arm32v6 都有的鏡像——好比 redis 鏡像 。shell

打開 Docker Hub : arm32v7/redis 能夠看到頁面上對鏡像的說明,包括鏡像的標籤(Tags),有的還會有如何使用鏡像的說明。數據庫

頁面右側還有如何獲取鏡像的命令:

$ docker pull arm32v7/redis
複製代碼

執行命令嘗試獲取鏡像,運行結果以下:

Using default tag: latest
latest: Pulling from arm32v7/redis
5ec7d30a9a8c: Pull complete
681a2ce24187: Pull complete
3cd0ed4f3f6d: Pull complete
3c6baf32ca8b: Pull complete
3730cf9f8869: Pull complete
3478618950f1: Pull complete
Digest: sha256:431418afc48dc6255060ccf6b157f5b555867bdd9486761631aecc961889860c
Status: Downloaded newer image for arm32v7/redis:latest
複製代碼

首先輸出的是使用了默認標籤 latest,而後就獲取鏡像的進度和最後獲取成功的狀態。

打開 Docker Hub : arm32v6/redis,能夠看到 arm32v6 的標籤比 arm32v7 少得多。一樣執行右側的獲取鏡像命令:

$ docker pull arm32v6/redis
複製代碼

結果返回的是找不到對應的 tag:

Using default tag: latest
Error response from daemon: manifest for arm32v6/redis:latest not found
複製代碼

打開arm32v6/redis 的 Tags 頁面能夠發現並無 latest 標籤,所以獲取時須要手動指定標籤:

$ docker pull arm32v6/redis:3.2.9-alpine
3.2.9-alpine: Pulling from arm32v6/redis
47c5ef52fac1: Pull complete
61fe9dee93c9: Pull complete
86ba91790149: Pull complete
20a8d3f1f622: Pull complete
1bc8cae18b26: Pull complete
4aa6ca97d16c: Pull complete
Digest: sha256:99ca1c5627328b6f5244fbafd7df89495cc5a5409533008285e659406526a95e
Status: Downloaded newer image for arm32v6/redis:3.2.9-alpine
複製代碼

再經過 docker images 命令能夠查看本地已經獲取的鏡像有哪些:

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
arm32v7/redis       latest              cbd04331e9ea        3 days ago          83.2MB
arm32v6/redis       3.2.9-alpine        8c7362c51d1c        3 months ago        15.8MB
armhf/hello-world   latest              d40384c3f861        11 months ago       1.64kB
複製代碼

總結

  • 可使用 docker pull 獲取鏡像
  • 默認獲取標籤爲 latest 的鏡像
  • 在鏡像名後面添加 : 加標籤名的方式獲取指定標籤的鏡像

Run

在 Part 1 末尾使用docker run --rm hello-world 時,能夠看終端中有以下輸出:

...
Unable to find image 'hello-world:latest' locally
latest: Pulling from hello-world
a0691bf12e4e: Pull complete
Digest: sha256:f5233545e43561214ca4891fd1157e1c3c563316ed8e237750d59bde73361e77
Status: Downloaded newer image for hello-world:latest
...
複製代碼

能夠看出 docker run 執行時會先從本地找要運行的鏡像,若是沒有,就會嘗試從遠程倉庫獲取鏡像。也就是能夠不使用 docker pull 獲取鏡像,而是直接 docker run 來運行。

接着運行獲取到的 arm32v7/redis 鏡像:

$ docker run -d --name arm32v7-redis arm32v7/redis
複製代碼

-d--name 都是 docker run 的參數。-d 表示之後臺方式運行容器,--name 則表示爲容器命名。

而後運行 arm32v6/redis 鏡像:

$ docker run -d --name arm32v6-redis arm32v6/redis:3.2.9-alpine
複製代碼

使用 docker ps 查看容器的運行狀態:

CONTAINER ID        IMAGE                        COMMAND                  CREATED             STATUS              PORTS               NAMES
9fc7c8edb071        arm32v6/redis:3.2.9-alpine   "docker-entrypoint..."   4 seconds ago       Up 2 seconds        6379/tcp            arm32v6-redis
0313638babf3        arm32v7/redis                "docker-entrypoint..."   28 minutes ago      Up 28 minutes       6379/tcp            arm32v7-redis
複製代碼

能夠看到兩個容器都是運行的狀態。若是想查看容器內容應用的日誌,可使用 docker logs arm32v7-redis

1:C 15 Sep 12:48:11.625 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1:C 15 Sep 12:48:11.625 # Redis version=4.0.1, bits=32, commit=00000000, modified=0, pid=1, just started
1:C 15 Sep 12:48:11.625 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
1:M 15 Sep 12:48:11.630 # Warning: 32 bit instance detected but no memory limit set. Setting 3 GB maxmemory limit with 'noeviction' policy now.
1:M 15 Sep 12:48:11.632 * Running mode=standalone, port=6379.
1:M 15 Sep 12:48:11.632 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
1:M 15 Sep 12:48:11.632 # Server initialized
1:M 15 Sep 12:48:11.632 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
1:M 15 Sep 12:48:11.632 * Ready to accept connections
複製代碼

這裏 arm32v7-redis 能夠是容器 ID(CONTAINER ID)也能夠是容器名,不過能查看容器內應用日誌的前提是stdoutstderr 的方式輸出信息

關於 stdoutstderr 能夠在《鳥哥的Linux 私房菜》中瞭解。

到這裏就有個問題了:

兩個 Redis 容器都是使用的默認配置——端口都是 6379,從日誌也能夠肯定兩個容器都是運行的,怎麼沒有產生衝突?若是使用客戶端鏈接容器內的 Redis 實例,鏈接的是哪個?

Network

如何讓外部應用能夠訪問容器的服務,就須要配置容器的網絡了。首先在樹莓派上執行 ip addr 命令來查看樹莓派的網卡信息:

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: enxxxxxxxxxxxxx: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
    inet 192.168.50.169/24 brd 192.168.50.255 scope global enxxxxxxxxxxxxx
       valid_lft forever preferred_lft forever
    inet6 fe80::3b4e:6e75:3cec:d1b6/64 scope link
       valid_lft forever preferred_lft forever
3: wlan0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN group default qlen 1000
    link/ether yy:yy:yy:yy:yy:yy brd ff:ff:ff:ff:ff:ff
4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:4b:bc:11:30 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 scope global docker0
       valid_lft forever preferred_lft forever
複製代碼

除了有線網卡 enxxxxxxxxxxxxx 與無線網卡 wlan0 以外還多了一個網段爲172.17.0.1/16docker0 的網卡。

接下來須要查看一下在運行的兩個容器的 IP 地址:

$ docker exec arm32v7-redis hostname -i
$ docker exec arm32v6-redis hostname -i
複製代碼

能夠看到兩個容器的 IP 並不同,可是與 docker0 網卡的網段一致,而後嘗試使用 Redis 客戶端鏈接 Redis 實例,能夠發現是沒法鏈接的,因此就不存在端口衝突。

先將運行中的容器停下來:

$ docker stop redis32v7-redis redis32v6-redis
複製代碼

使用 docker ps 是看不到運行的容器了,不過 docker ps -a 仍是能看到,能夠用下面的命令刪除掉中止的容器:

$ docker rm redis32v7-redis redis32v6-redis
複製代碼

而後添加新的參數來運行容器:

$ docker run -d --name arm32v6-redis -p 6379:6379 arm32v6/redis:3.2.9-alpine
複製代碼

注意:由於 arm32v7/redis 鏡像沒有 ip 命令,這裏須要使用 arm32v6/redis 鏡像。

再來查看一下運行狀態:

CONTAINER ID        IMAGE                        COMMAND                  CREATED             STATUS              PORTS                    NAMES
a0423d07eaae        arm32v6/redis:3.2.9-alpine   "docker-entrypoint..."   2 minutes ago       Up 2 minutes        0.0.0.0:6379->6379/tcp   arm32v6-redis
複製代碼

能夠發現增長了-p 參數以後,運行狀態中 PORTS 發生了變化。若是再以相同的參數運行 arm32v7/redis 鏡像,就會獲得端口已經佔用的提示:

37b2d6758655146b0dd1062b3ab6e56113bcaf4af0278d62f975fc44baad2c17
docker: Error response from daemon: driver failed programming external connectivity on endpoint arm32v7-redis (cea1de660470e80efd513c44dbb60b180b42b01c71cf8262f216290b7f2b37a1): Bind for 0.0.0.0:6379 failed: port is already allocated.
複製代碼

而後使用下面的命令在容器 arm32v6-redis 中執行 ip addr 命令:

$ docker exec arm32v6-redis ip addr
...
31: eth0@if32: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 scope global eth0
       valid_lft forever preferred_lft forever
複製代碼

-p 參數的做用就是將容器端口與主機端口綁定,第一個端口表示主機端口,二個端口表示容器端口。外部應用訪問容器應用時應該使用主機端口訪問,而不是容器端口。

除了 -p 參數還可使用 --net 的方式來配置容器的網絡。

將已經運行的容器中止並刪除以後,用下面的命令啓動新的容器:

$ docker run -d --name arm32v6-redis --net host arm32v6/redis:3.2.9-alpine
複製代碼

在來查看容器運行的狀態:

CONTAINER ID        IMAGE                        COMMAND                  CREATED             STATUS              PORTS               NAMES
41500fe5d148        arm32v6/redis:3.2.9-alpine   "docker-entrypoint..."   31 seconds ago      Up 30 seconds                           arm32v6-redis
複製代碼

PORTS 又發生了變化,再次經過 docker exec arm32v6-redis ip addr 命令查看容器的網卡信息:

...
2: enxxxxxxxxxxxxx: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
    inet 192.168.50.167/24 brd 192.168.50.255 scope global enxxxxxxxxxxxxx
       valid_lft forever preferred_lft forever
    inet6 fe80::fe0c:bf33:4bcc:d3c7/64 scope link
       valid_lft forever preferred_lft forever
...
複製代碼

與以前使用 -p 參數比多了一個網卡 enxxxxxxxxxxxxx,並且這個網卡是與樹莓派的網卡名是同樣的。

能夠看出參數 --net host 將容器網絡與主機網卡綁定了——也就是主機模式,而 -p 參數只是映射指定的端口。 根據實際須要,使用這兩個參數讓容器內的服務變成能夠訪問的狀態。

總結

  • 沒有參數聲明時容器與主機、容器與容器間的網絡是隔離的。
  • 使用 -p 能夠將容器的網絡端口暴露出來,與路由器的 NAT 相似。
  • 使用 --net 可讓容器直接使用主機的網絡。

Volume

使用下面的命令進入到容器內部的終端:

$ docker exec -ti arm32v6-redis sh
複製代碼

注:根據不一樣的鏡像運行的容器,解釋器也有可能不一樣,根據實際狀況,sh 也能夠換成 bash

在容器終端中使用命令建立一個文件:

/data # touch test
複製代碼

首先重啓容器:

$ docker restart arm32v6-redis
複製代碼

再進入容器終端查看 test 文件仍是存在的。

而後,中止並刪除容器 arm32v6-redis,從新建立一個同名容器,再進入容器查看,發現 test 文件不存在了。若是想將容器運行時產生的文件持久化,就須要使用 -v 參數將容器路徑掛載到主機路徑。

中止並刪除容器 arm32v6-redis 以後,使用下面的命令從新建立容器:

$ docker run -d --name arm32v6-redis -v $(pwd):/data -p 6379:6379 arm32v6/redis:3.2.9-alpine
複製代碼

注:$(pwd) 表示獲取當前路徑。若是在用戶主目錄下運行,則等價於

$ docker run -d --name arm32v6-redis -v /home/pi:/data -p 6379:6379 arm32v6/redis:3.2.9-alpine
複製代碼

接着使用一樣的方法在容器內建立 test 文件,退出容器終端,以後查看本機對應的路徑:

pi@raspberrypi:~ $ ls -l
total 0
-rw-r--r-- 1 root root 0 Sep 16 16:29 test
複製代碼

能夠看到本地路徑下出現了建立的 test 文件,顯然若是刪除容器並將一樣的路徑掛載,test 文件是不會丟失的。

注:因爲在容器內部時是 root 用戶,因此建立文件全部者也是 root 用戶。 Linux 權限是以 ID 爲準,也有些鏡像並不是默認 root 權限運行內部應用,這時掛載路徑須要根據鏡像說明配置正確的權限才能讓容器內部應用正常運行。

除了使用 -v 參數指定掛載路徑,也能夠建立 volume 而後掛載:

$ docker volume create redis-data
$ docker run -d --name arm32v6-redis -v redis-data:/data -p 6379:6379 arm32v6/redis:3.2.9-alpine
複製代碼

關於 Docker Volume 能夠在文檔查看更多信息。

總結

  • 容器的修改不會持久化到鏡像
  • 使用 -v 參數能夠將主機路徑掛載到容器中
  • 使用 Docker Volume 也能夠實現容器數據的持久化

實踐

如何使用 Docker 部署服務所須要的知識都已經具有了,好比想運行一個 PostgreSQL 數據庫服務。

首先是在 Docker Hub/Store 上找到鏡像 hub.docker.com/r/arm32v7/p…

接着獲取對應標籤的鏡像,也能夠直接 docker run

$ docker pull arm32v7/postgres:9.6.5
複製代碼

接着建立 Volume,或者在運行時經過 -v 參數指定掛載路徑:

$ docker volume create pgdata
複製代碼

使用 docker volume inspect pgdata 能夠查看建立的 Volume 的路徑:

[
    {
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/pgdata/_data",
        "Name": "pgdata",
        "Options": {},
        "Scope": "local"
    }
]
複製代碼

運行 arm32v7/postgres 鏡像是須要經過變量來指定使用好比數據庫密碼、端口、數據路徑等信息:

$ docker run --name arm32v7-pgsql \
 -e POSTGRES_DB=mydb \
 -p 5432:5432 \
 --user root \
 -v pgdata:/var/lib/postgresql/data \
 -d arm32v7/postgres:9.6.5
複製代碼

注:這裏使用 --user root 是偷懶不處理文件權限問題。

容器運行後能夠查看 Volume 下的變化:

sudo ls /var/lib/docker/volumes/pgdata/_data
複製代碼

也能夠進入容器內部終端,使用 psql 命令:

$ docker exec -ti arm32v7-pgsql bash
複製代碼
root@90228514f956:/# psql -h localhost -U postgres -d mydb
psql (9.6.5)
Type "help" for help.

mydb=# \l
                                 List of databases
   Name    |  Owner   | Encoding |  Collate   |   Ctype    |   Access privileges
-----------+----------+----------+------------+------------+-----------------------
 mydb      | postgres | UTF8     | en_US.utf8 | en_US.utf8 |
 postgres  | postgres | UTF8     | en_US.utf8 | en_US.utf8 |
 template0 | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +
           |          |          |            |            | postgres=CTc/postgres
 template1 | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +
           |          |          |            |            | postgres=CTc/postgres
(4 rows)
複製代碼

注:若是要設定數據庫用戶和口令則須要在啓動容器時添加對應的變量 POSTGRES_PASSWORDPOSTGRES_USER,詳細的使用方法能夠在 arm32v7/postgres 鏡像頁面上查看。

docker run 經常使用的方法就是這些了,到這裏就能夠學會如何獲取鏡像,而且按照通常須要運行起來了。

若是想了解更多的相關參數能夠查看 Docker 官方文檔瞭解。

相關文章
相關標籤/搜索