2017/5/8

docker for mac


docker 在 mac 上原本是用 boot2docker,但現在已經有了原生的 Docker for mac,Docker for mac 不再是使用 VirtualBox,而是改用比較輕量化的 macOS 虛擬機 HyperKit。


Docker for mac 需要 OS X El Capitan 10.11 以上的作業系統,以及 2010 或更新 Mac 硬體,以支援 MMU virtualization。


Get started with Docker for Mac 這個網頁可以下載 docker,目前分為 Stable 及 Beta 兩個版本,我們是安裝 Stable 版本,但還是隨時可以切換到 Beta 版。


安裝完成並執行後,可以在狀態列看到 docker 那隻鯨魚。



docker 的元件


  1. 客戶端及伺服器: client 發送 request 給 docker daemon,由 damone 代理工作並回傳結果


  1. image: 由指令一步一步建構出來的 image,類似 container 的 source code

  2. registry: docker 以 registry 保存 image,分為 public 及 private 兩種,公用的 registry 稱為 docker hub,可以自己架設 private registry。

  3. container: image 是 docker 的建構及打包對象,而 container 是啟動跟執行的目標。


docker 的技術元件


  1. libcontainer: 就是原生的 Linux 容器
  2. 獨立的 kernel namespace 用來隔離文件、process、網路
  3. 每個 container 都有自己的 /root,獨立的文件系統
  4. 獨立的 process
  5. 獨立的網路
  6. 資源隔離及分組: 使用 cgroups 將 CPU 及記憶體獨立分配給每個 container
  7. copy-on-write: 分層的文件系統,但 user 還是只能看到所有文件疊加後的結果,會覺得只有一個文件系統


  8. log: container 的 STDIN, STDOUT, STDERR 都會記錄到 log中

  9. 互動式 shell: 可建立虛擬的 tty terminal,連接到 STDIN


測試 docker


我們可以利用 ubuntu 的 image,執行一個 echo 指令進行 docker 的測試,在安裝了 docker for mac 之後,就可以直接在 terminal 使用 docker 的 command line tool。


$ docker run -it ubuntu echo "Hello World"
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
b3e1c725a85f: Pull complete
4daad8bdde31: Pull complete
63fe8c0068a8: Pull complete
4a70713c436f: Pull complete
bd842a2105a8: Pull complete
Digest: sha256:7a64bc9c8843b0a8c8b8a7e4715b7615e4e1b0d8ca3c7e7a76ec8250899c397a
Status: Downloaded newer image for ubuntu:latest
Hello World

我們也可以直接執行一個 nginx web server


$ docker run -d -p 80:80 --name webserver nginx
nable to find image 'nginx:latest' locally
latest: Pulling from library/nginx
75a822cd7888: Already exists
64f0219ba3ea: Pull complete
325b624bee1c: Pull complete
Digest: sha256:2a07a07e5bbf62e7b583cbb5257357c7e0ba1a8e9650e8fa76d999a60968530f
Status: Downloaded newer image for nginx:latest
50f78171d0e26922a0b01025125a231b33615086119ebf3e8a0846b2b297760b

啟動了 web server,就可以用 browser 連結到 http://localhost/ 瀏覽網頁。


使用 Kitematic 管理 docker container


docker for mac 並沒有將 kitematic 放進去,而是讓去下載 Kitematic ,下載後會取得 Kitematic-Mac.zip,直接用以下的指令將 kitematic 解壓縮到 Applications 目錄中。


sudo unzip ~/Downloads/Kitematic-Mac.zip -d /Applications


我們可以看到剛剛執行的那個 ubuntu 的 container。



一些 docker 的基本指令


docker 容器有兩種,一種是 interactive container,
可以用互動的方式進行操作,一種是 daemonized container,可以執行應用服務 server。


執行一個名稱為 u1 的容器,-i 是保證容器有打開 STDIN,-t 是產生一個虛擬的 tty 終端,此容器使用 ubuntu 這個 image,然後執行 bash,在 bash shell 中以 exit 離開時,這個容器就會停止。可以在 bash 中執行 ls, ps -aux 等等指令。


docker run --name u1 -i -t ubuntu /bin/bash

啟動剛剛建立的 u1 container


docker start u1

重新附著到 u1,也就是進入 u1 的 bash


docker attach u1



利用 while,產生一個一直列印 hello world 的 daemon server


docker run --name d1 -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done"

查看正在執行的 container


$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS               NAMES
a0d673b448a8        ubuntu              "/bin/sh -c 'while tr"   About a minute ago   Up About a minute                       d1

列出所有的 containers,包含執行跟停止中的


docker ps -a

查看 d1 的 log


docker logs d1
# 類似 tail -f
docker logs -f d1

停止 d1


docker stop d1

產生一個新的 d2 container(新的容器名稱不能跟舊的一樣),不管遇到容器的(bash)退出碼是多少,都會自動重新啟動,也可以用 --restart=on-failure:5 來限制退出碼非0時,重新啟動,且只會重啟動 5 次。


docker run --restart=always --name d2 -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done"

可用 inspect 查看 d2 的資訊


$ docker inspect d2
[
    {
        "Id": "8dba5cf2a6ef94143f460bb6be37342d0e14bdae3d3d02645736ab1c9f343621",
        "Created": "2016-12-22T01:57:38.837925059Z",
        "Path": "/bin/sh",
        "Args": [
            "-c",
            "while true; do echo hello world; sleep 1; done"
        ],
        "State": {
            "Status": "running",
            "Running": true,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 2799,
            "ExitCode": 0,
            "Error": "",
            "StartedAt": "2016-12-22T01:57:39.765031056Z",
            "FinishedAt": "0001-01-01T00:00:00Z"
        },
        "Image": "sha256:104bec311bcdfc882ea08fdd4f5417ecfb1976adea5a0c237e129c728cb7eada",
        "ResolvConfPath": "/var/lib/docker/containers/8dba5cf2a6ef94143f460bb6be37342d0e14bdae3d3d02645736ab1c9f343621/resolv.conf",
        "HostnamePath": "/var/lib/docker/containers/8dba5cf2a6ef94143f460bb6be37342d0e14bdae3d3d02645736ab1c9f343621/hostname",
        "HostsPath": "/var/lib/docker/containers/8dba5cf2a6ef94143f460bb6be37342d0e14bdae3d3d02645736ab1c9f343621/hosts",
        "LogPath": "/var/lib/docker/containers/8dba5cf2a6ef94143f460bb6be37342d0e14bdae3d3d02645736ab1c9f343621/8dba5cf2a6ef94143f460bb6be37342d0e14bdae3d3d02645736ab1c9f343621-json.log",
        "Name": "/d2",
        "RestartCount": 0,
        "Driver": "aufs",
        "MountLabel": "",
        "ProcessLabel": "",
        "AppArmorProfile": "",
        "ExecIDs": null,
        "HostConfig": {
            "Binds": null,
            "ContainerIDFile": "",
            "LogConfig": {
                "Type": "json-file",
                "Config": {}
            },
            "NetworkMode": "default",
            "PortBindings": {},
            "RestartPolicy": {
                "Name": "always",
                "MaximumRetryCount": 0
            },
            "AutoRemove": false,
            "VolumeDriver": "",
            "VolumesFrom": null,
            "CapAdd": null,
            "CapDrop": null,
            "Dns": [],
            "DnsOptions": [],
            "DnsSearch": [],
            "ExtraHosts": null,
            "GroupAdd": null,
            "IpcMode": "",
            "Cgroup": "",
            "Links": null,
            "OomScoreAdj": 0,
            "PidMode": "",
            "Privileged": false,
            "PublishAllPorts": false,
            "ReadonlyRootfs": false,
            "SecurityOpt": null,
            "UTSMode": "",
            "UsernsMode": "",
            "ShmSize": 67108864,
            "Runtime": "runc",
            "ConsoleSize": [
                0,
                0
            ],
            "Isolation": "",
            "CpuShares": 0,
            "Memory": 0,
            "CgroupParent": "",
            "BlkioWeight": 0,
            "BlkioWeightDevice": null,
            "BlkioDeviceReadBps": null,
            "BlkioDeviceWriteBps": null,
            "BlkioDeviceReadIOps": null,
            "BlkioDeviceWriteIOps": null,
            "CpuPeriod": 0,
            "CpuQuota": 0,
            "CpusetCpus": "",
            "CpusetMems": "",
            "Devices": [],
            "DiskQuota": 0,
            "KernelMemory": 0,
            "MemoryReservation": 0,
            "MemorySwap": 0,
            "MemorySwappiness": -1,
            "OomKillDisable": false,
            "PidsLimit": 0,
            "Ulimits": null,
            "CpuCount": 0,
            "CpuPercent": 0,
            "IOMaximumIOps": 0,
            "IOMaximumBandwidth": 0
        },
        "GraphDriver": {
            "Name": "aufs",
            "Data": null
        },
        "Mounts": [],
        "Config": {
            "Hostname": "8dba5cf2a6ef",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "no_proxy=*.local, 169.254/16",
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
            ],
            "Cmd": [
                "/bin/sh",
                "-c",
                "while true; do echo hello world; sleep 1; done"
            ],
            "Image": "ubuntu",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": {}
        },
        "NetworkSettings": {
            "Bridge": "",
            "SandboxID": "ea7a45d1dcfb681283e18aad9feaaedd98da8cc8cd4155ca2284e4aeb6db640f",
            "HairpinMode": false,
            "LinkLocalIPv6Address": "",
            "LinkLocalIPv6PrefixLen": 0,
            "Ports": {},
            "SandboxKey": "/var/run/docker/netns/ea7a45d1dcfb",
            "SecondaryIPAddresses": null,
            "SecondaryIPv6Addresses": null,
            "EndpointID": "48480731d5cb9ab9e268e341c65fd924671222660ffa58a6adc61734811bae83",
            "Gateway": "172.17.0.1",
            "GlobalIPv6Address": "",
            "GlobalIPv6PrefixLen": 0,
            "IPAddress": "172.17.0.2",
            "IPPrefixLen": 16,
            "IPv6Gateway": "",
            "MacAddress": "02:42:ac:11:00:02",
            "Networks": {
                "bridge": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": null,
                    "NetworkID": "b7f0ca41f518b121beb774aac41e709b5032e7cff96939e43e3ddac1ed16cf98",
                    "EndpointID": "48480731d5cb9ab9e268e341c65fd924671222660ffa58a6adc61734811bae83",
                    "Gateway": "172.17.0.1",
                    "IPAddress": "172.17.0.2",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:ac:11:00:02"
                }
            }
        }
    }
]

配合 format 查看特定的資訊


$ docker inspect --format='{{ .State.Running }}' d2
true
$ docker inspect --format='{{ .NetworkSettings.IPAddress }}' d2
172.17.0.2

$ docker inspect --format='{{.Name}} {{ .State.Running }}' d1 d2
/d1 false
/d2 true

先停止 d2 後,才能用 rm 刪除 d2


docker stop d2
docker rm d2



列出所有 images


docker images

取得 ubuntu 的 image


docker pull ubuntu

以 TAG 指定 ubuntu 的版本


docker run --name t1 -t -i ubuntu:12.04 /bin/bash

列出 ubuntu 的 images


$ docker images ubuntu
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              latest              104bec311bcd        6 days ago          129 MB
ubuntu              12.04               f0d07a756afd        6 days ago          103.6 MB

要先移除跟 image 相關的 containers,才能移除 image


docker rm t1

docker rmi ubuntu:12.04

搜尋所有 docker hub 的公用可用 image


docker search puppet

建立 docker image


有兩種方式:


  1. 使用 docker commit: 不建議用這種方式


    登入 docker hub


    $docker login
    Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
    Username: yaocl
    Password:
    Login Succeeded

    執行一個 ubuntu 的 container


    docker run -i -t ubuntu /bin/bash

    在容器中安裝 apache


    apt-get -yqq update
    apt-get -y install apache
    exit

    將 container 以 commit 存成另一個 image


    $ docker commit fd915f899feb yaocl/apache2
    sha256:32421625c13d3076c90a18d3146dab180a9ed5c33e8439b5572acdcf7a328e02
    
    $ docker images yaocl/apache2
    REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
    yaocl/apache2       latest              32421625c13d        31 seconds ago      267.6 MB

    自訂一些資訊欄位,產生新的 image tag


    $ docker commit -m="custom image" --author="yaocl" fd915f899feb yaocl/apache2:webserver
    sha256:b2e0f5faed623a8f62bdad08223773ae85274c9fc40e12753ac1df1258152f2f
    
    $ docker images yaocl/apache2
    REPOSITORY          TAG                 IMAGE ID            CREATED              SIZE
    yaocl/apache2       webserver           b2e0f5faed62        About a minute ago   267.6 MB
    yaocl/apache2       latest              32421625c13d        2 minutes ago        267.6 MB

    使用新的 yaocl/apache2:webserver image


    docker run -t -i yaocl/apache2:webserver /bin/bash
  2. 使用 dokcer build 及 Dockerfile 文件


    static_web 目錄就是 dokcer 的 build environment,此環境就稱為 context 或 build context,先在這個 context 中建立一個 Dockerfile


    mkdir static_web
    cd static_web/
    touch Dockerfile

    編輯 Dockerfile 內容


    # Version 0.0.1
    FROM ubuntu:latest
    MAINTAINER yaocl
    ENV REFRESHED_AT 2016/12/22
    RUN apt-get update
    RUN apt-get install -y nginx
    RUN echo "hi from container" > /usr/share/ngnix/html/index.html
    EXPOSE 80

    以 build 指令建立 image,如果沒有寫 :v1,就是預設的 latest 這個 tag


    cd static_web/
    #docker build -t="yaocl/static_web" .
    
    docker build -t="yaocl/static_web:v1" .

    建立 images 時發生錯誤


    Setting up nginx (1.4.6-1ubuntu3.7) ...
    Processing triggers for libc-bin (2.19-0ubuntu6.9) ...
    Processing triggers for sgml-base (1.26+nmu4ubuntu1) ...
     ---> 83f7d5f1ad20
    Removing intermediate container 2681f1e39e14
    Step 5 : RUN echo "hi from container"   > /usr/shre/ngnix/html/index.html
     ---> Running in f1bd373dc64e
    /bin/sh: 1: cannot create /usr/shre/ngnix/html/index.html: Directory nonexistent
    The command '/bin/sh -c echo "hi from container"    > /usr/shre/ngnix/html/index.html' returned a non-zero code: 2

    以 docker images 可以看到錯誤步驟前那一個 image id: 83f7d5f1ad20


    $ docker images
    REPOSITORY                      TAG                 IMAGE ID            CREATED             SIZE
    <none>                          <none>              83f7d5f1ad20        39 seconds ago      231.1 MB

    以該 image id 執行一個 container


    $ docker run --name test -i -t 83f7d5f1ad20 /bin/bash

    發現是路徑寫錯了 /usr/share/nginx/html


    修改 Dockerfile 內容


    # Version 0.0.1
    FROM ubuntu:latest
    MAINTAINER yaocl
    ENV REFRESHED_AT 2016/12/22
    RUN apt-get update
    RUN apt-get install -y nginx
    RUN echo "hi from container" > /usr/share/nginx/html/index.html
    EXPOSE 80

    再 build 一次,就成功了


    $ docker build -t="yaocl/static_web:v1" .
    Sending build context to Docker daemon 2.048 kB
    Step 1 : FROM ubuntu:latest
     ---> 3f755ca42730
    Step 2 : MAINTAINER yaocl
     ---> Using cache
     ---> 062ac75f50aa
    Step 3 : RUN apt-get update
     ---> Using cache
     ---> 43e1cecf45be
    Step 4 : RUN apt-get install -y nginx
     ---> Using cache
     ---> 83f7d5f1ad20
    Step 5 : RUN echo "hi from container" > /usr/share/nginx/html/index.html
     ---> Running in 9a1dcbadd174
     ---> 11fa4a76019d
    Removing intermediate container 9a1dcbadd174
    Step 6 : EXPOSE 80
     ---> Running in 2d0db9f90d57
     ---> 683486e4c00a
    Removing intermediate container 2d0db9f90d57
    Successfully built 683486e4c00a
    [14:10]charley@cmbp ~/Downloads/static_web$ docker images
    REPOSITORY                      TAG                 IMAGE ID            CREATED             SIZE
    yaocl/static_web                v1                  683486e4c00a        4 seconds ago       231.1 MB

    由於 docker 會在每一個步驟都產生 image,可以加上 --no-cache 避免產生這個問題


    docker build --no-cache -t="yaocl/static_web:v1" .

    產生 yaocl/static_web:v1 的 container


    $ docker run --name t1 -d -p 80 yaocl/static_web:v1 nginx -g "daemon off;"
    4dbe731715cb046c6c12bb23264e9b4add0631948b3dfa15cb4e740a586eed68

    以 ps 指令查看主機隨機以 49153 ~ 65535 任意一個 port 對應到 80 的資訊


    $ docker ps
    CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS                   NAMES
    4dbe731715cb        yaocl/static_web:v1   "nginx -g 'daemon off"   29 seconds ago      Up 28 seconds       0.0.0.0:32768->80/tcp   t1

    瀏覽器可以連結到 http://localhost:32768 查看網頁


    如果是 -p 80:80 就是使用主機的 80


    $ docker run --name t1 -d -p 80:80 yaocl/static_web:v1 nginx -g "daemon off;"

    綁定主機的 127.0.0.1:80


    $ docker run --name t1 -d -p 127.0.0.1:80:80 yaocl/static_web:v1 nginx -g "daemon off;"

Dockerfile 指令


Dockerfile reference


  1. CMD


    指定 container 最後要執行的命令,也可以用 docker run 覆蓋 CMD 的指令


    CMD ["nginx", "-g", "daemon off;"]

    可以省略最後用 /bin/sh -c 執行的指令


    $ docker run --name t2 -d -p 127.0.0.1:80:80 yaocl/static_web:v2
  2. ENTRYPOINT


    跟 CMD 類似但不一樣,使用 ENTRYPOINT 可以接受 run 後面增加的參數


    ENTRYPOINT ["/usr/sbin/nginx"]

    執行時會增加後面 -g "daemon off;" 的參數給 nginx


    $ docker run --name t2 -d -p 127.0.0.1:80:80 yaocl/static_web:v2 -g "daemon off;"

    所以實際上使用會是


    ENTRYPOINT ["/usr/sbin/nginx"]
    CMD ["-g", "daemon off;"]

    或是在沒有參數時,列印 help 資訊


    ENTRYPOINT ["/usr/sbin/nginx"]
    CMD ["-h"]
  3. WORKDIR


    在 container 內設定工作目錄,執行 ENTRYPOINT 或 CMD


    使用時可以在過程中隨時切換目錄


    WORKDIR /opt/webapp/db
    RUN bundle install
    WORKDIR /opt/webapp
    ENTRYPOINT ["rackup"]
  4. ENV


    設定環境變數,可以在 docker run 以 -e 參數指定環境變數


    ENV RVM_PATH /home/rvm/
    
    ENV TARGET_DIR /opt/app
    WORKDIR $TARGET_DIR

    docker run -i -t -e "WEB_PORT=8080" ubuntu env
  5. USER


    指定用什麼帳號身份執行指令,可以用 docker run 的 -u 參數覆蓋這個設定,如果沒有指定,預設為 root


    USER user
    USER user:group
    USER uid
    USER uid:gid
    USER uid:group
  6. VOLUME


    對 container 增加 volume,一個 volume 可同時存在於一個或多個 container 的特定目錄,該目錄可繞過聯合文件系統,進行資料共享或持久化的工作


    • volume 可在 container 之間共享, 重用
    • container 不一定要跟其他 container 共享 volume
    • 對 volume 修改資料會立即生效
    • 修改 volume 裡面的資料,不會對 image 產生影響
    • volume 會一直存在,直到沒有任何 container 使用它

    VOLUME ["/opt/project", "/data"]
  7. ADD


    將建構環境中的檔案複製到 image 中,後面的目的地檔名不能省略,新目錄或檔案權限都是 0755,UID及 GID 都是 0


    ADD sw.lic /opt/application/sw.lic
    ADD http://wordpress.org/latest.zip /root/wordpress.zip

    這會自動將 latest.tar.gz 解壓縮到目的地的目錄中


    ADD latest.tar.gz /var/www/wordpress/
  8. COPY


    類似 ADD,但沒有解壓縮的功能


    COPY conf.d/ /etc/apache2/
  9. ONBUILD


    為 image 增加 trigger 的功能,當 image 被使用時,就會觸發 trigger


    ONBUILD ADD . /app/src
    ONBUILD RUN cd /app/src && make

Docket hub 與私有的 Docker Registry


push image to Docker hub


docker push static_web

也可以在 docket hub 增加 github/BitBucket 帳號連結,然後製作 Automated Build 自動產生 image




用這個指令可以產生一個 private docker registry


docker run -p 5000:5000 registry

$ docker images yaocl/static_web
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
yaocl/static_web    v2                  1d48b7f306d8        2 hours ago         231.1 MB
yaocl/static_web    v1                  ce19993fc181        3 hours ago         231.1 MB 

將 image push 到 docker.example.com:5000 這個 private registry


docker tag 1d48b7f306d8 docker.example.com:5000/yaocl/static_web

docker run -t -i docket.example.com:5000/yaocl/static_web /bin/bash

scripts


移除所有 stopped containers


docker rm $(docker ps -a -q)

移除所有 UNTAG images


docker rmi $(docker images | grep "<none>" | awk '{print $3}')

References


[Docker] 在 Mac 上使用原生的 Docker for Mac 操作 container


撰寫一份符合需求的 Dockerfile