我的docker學習筆記

近來發現docker越來越受歡迎了,而且看起來,docker的熱度還遠沒有到達頂峯,或許在這個雲服務時代的大背景下,docker會是另一個轉折點。這裏暫且將這幾天看到的一些小知識點整理下,方便自己後面複習吧。


安裝docker

docker雖然是跨平臺的,但是很明顯在linux上工作起來要比在Windows上方便的多,所以我就在自己的雲服務器上裝一個好了。

  • 包管理器:$sudo apt-get install docker-ce, 關於新(docker-ce),舊(docker, docker-io)版本,這裏就不多說了,個人覺得版本還是LTS的最新版比較好。
  • shell方式安裝: $ wget -qO- https://get.docker.com/ | sh

到這裏基本上,環境準備工作就算是結束了,然後就可以開始docker之旅了。

概念

在正式使用docker之前,還是得先了解下關於docker的一些關鍵字。

  • 鏡像:簡單來類比思考,docker的鏡像就像是一個ISO文件,被docker引擎加載之後就相當於是一個系統。但是實際上一個docker鏡像卻遠遠沒有我們平時看到的系統鏡像那麼大,這是因爲docker對鏡像的分層構建(底層是Union FS技術)實現的。因此,一個docker其實就是一系列文件的集合,被一層層的構建起來的。具體的例子也很好表述:我們在pull下來一個系統鏡像之後,肯定是爲了定製來滿足自己的開發需求的,期間會在安裝一些軟件或者搭建環境,這就相當於在原來的鏡像基礎上又包裝了一層文件系統。我們所安裝的軟件會持久化到當前的鏡像中,如果此鏡像被構建成新的鏡像,別人pull了之後就可以直接使用。因此每一層的構建都最好不要把不必要的“文件”放進去,以免鏡像越來越大。

  • 容器:類似於程序和進程,鏡像是一個文件集合,而容器就是實際運行起來的可以被創建,開啓,終止,暫停,刪除的實體。或者這樣形容,鏡像和容器其實就是類和示例的翻版。docker的容器實際上是一個獨立的進程,獨立於宿主的操作系統下的實際“跑”起來的鏡像。正是由於它與宿主系統的隔離特性,因此docker會很安全。即使是整個服務崩掉或者被攻擊,也只是docker容器本身的系統宕掉。不會對宿主機器造成過大的影響。

  • 倉庫:顧名思義,倉庫是一個保存某種數據的存在。類似於git倉庫,這裏當然是保存docker的鏡像了。鏡像通過添加的標籤的方式來保存,具體的格式爲:<倉庫名>:<tag名>。如果tag名沒有具體描述,則使用latest作爲缺省名。類似於git倉庫,docker的倉庫通常也會是兩段式路徑的方式存在。舉例如下:

    • git: username/repo-name
    • docker: username/software, 第二個參數具體是什麼名稱也視具體情況而定。

    關於默認倉庫,國內外有很多網站都提供技術支持,按自己的需求去搜索即可。

  • 鏡像id和容器id:鏡像id和容器id就像是相對於二者的一個唯一的標識符,一般是一個12位長度的字符串,配合下面的命令可以實現一系列的操作。

總的來看,現在很多軟件以及體系的結構都是類似的,docker也不例外。在學習的過程中,不妨將docker與自己已經瞭解的一些概念做下類比,這樣不僅有助於瞭解軟件的生態體系,也方便後序的理解。

常用命令

  • 在命令行輸入docker --help會看到相關的命令參數,
  • 輸入docker subcommand --help 來查看具體的子命令的使用幫助。

舉例如下:

guo@Server218:~$ docker pull --help

Usage:  docker pull [OPTIONS] NAME[:TAG|@DIGEST]

Pull an image or a repository from a registry

Options:
  -a, --all-tags                Download all tagged images in the repository
      --disable-content-trust   Skip image verification (default true)
      --help                    Print usage

這樣就可以查看具體的某一個命令的使用方式了,這對於後面熟練的使用docker是比較有幫助的。接下來,我就記錄下我這兩天用到的一些簡單的命令的使用,很基礎,但是也很重要。

docker pull

pull 是一個動詞,中文意思爲“拉”,這裏可以理解爲將一個鏡像從遠端倉庫(具體也可以自己配置,默認是官方鏡像倉庫)下載到本地。docker在pull之前會檢查本地是否已經存在了是否下載過此鏡像,如果沒有本地確實沒有下載過,纔會到遠端倉庫進行下載。

guo@Server218:~$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntunginx         latest              ca3292d807d3        41 hours ago        177 MB
redis               3.2                 e97b1f10d81a        2 weeks ago         99.7 MB
ubuntu              latest              452a96d81c30        3 weeks ago         79.6 MB
hello-world         latest              e38bc07ac18e        5 weeks ago         1.85 kB
guo@Server218:~$ docker pull debian
Using default tag: latest
latest: Pulling from library/debian
cc1a78bfd46b: Pull complete 
Digest: sha256:de3eac83cd481c04c5d6c7344cd7327625a1d8b2540e82a8231b5675cef0ae5f
Status: Downloaded newer image for debian:latest
guo@Server218:~$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntunginx         latest              ca3292d807d3        41 hours ago        177 MB
redis               3.2                 e97b1f10d81a        2 weeks ago         99.7 MB
debian              latest              8626492fecd3        3 weeks ago         101 MB
ubuntu              latest              452a96d81c30        3 weeks ago         79.6 MB
hello-world         latest              e38bc07ac18e        5 weeks ago         1.85 kB
centos              latest              e934aafc2206        6 weeks ago         199 MB
guo@Server218:~$ 

docker pull 下來之後不會運行,僅僅是把一個鏡像下載到本地。而run命令則提供了更多的功能。

docker run

下面我們以編程語言屆最典型的例子Hello world來對docker run舉例。

guo@Server218:~$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntunginx         latest              ca3292d807d3        41 hours ago        177 MB
redis               3.2                 e97b1f10d81a        2 weeks ago         99.7 MB
ubuntu              latest              452a96d81c30        3 weeks ago         79.6 MB
guo@Server218:~$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
9bb5a5d4561a: Already exists 
Digest: sha256:f5233545e43561214ca4891fd1157e1c3c563316ed8e237750d59bde73361e77
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/engine/userguide/

guo@Server218:~$ 

可見,run命令會首先做一下pull的處理,然後再加載鏡像到docker引擎中運行。

docker本身命令

  • docker version: 打印當前系統docker本身的版本信息。
  • docker info: 可以看做是更加詳細的docker version
  • docker ps類似於linux上查看進程的命令,docker ps用來查看當前運行了那些容器,以及對應的具體服務信息。這條命令對於查看容易的健康狀態是非常重要的,而且特別的常用。

操作鏡像

  • docker images: 列出本地已經下載的鏡像,類似於Python的pip list
  • docker search: 列出可以下載的對應名稱的鏡像列表,類似於Python的pip search
    等等

操作容器

相對於操作鏡像,docker對容器的操作命令更多,功能也更強大。
- docker stop/restart/pause/start: 停止,重啓,暫停,開啓一個容器
- docker rmi 通過容器id刪除一個已經存在的容器,一般要先停止容器才能操作,或者添加--force選項來強制刪除。
- docker logs 容器異常退出時,可以通過logs命令來查看具體的錯誤纖細,這一點對於服務的調試很有幫助。
等等

docker attachdocker exec

這兩個命令都是爲了進入一個正在運行的容器而存在的,但是卻有很大的不同。從字面意思一上來看其實也有點差距。attach給人的印象就像tmux的attach一樣,退出的時候會把正在運行的docker容器也殺死。而exec就像是SSH一樣,只是斷開“遠程連接”,即使是退出也不會導致當前正在運行的容器進程kill掉。單純用文字來講,描述起來可能也不清楚,下面還是來舉個小例子。

因爲docker本身的性質,如果一個命令運行完進程就會結束的話,docker容器自然也會結束。所以我們不能再容器中簡單的使用類似於echo hello這樣的bash命令,而是要使用一個會在後臺長期運行的命令,這樣才能保證容器一直在運行,所以我pull了一個Debian系統的redis鏡像。在容器中會啓動一個redis-server服務。

docker run -p 9999:6379 -v $PWD/data:/data -d redis:3.2 redis-server --appendonly yes

通過docker ps命令查看目前docker引擎內正在運行的容器的進程情況,可以看出確實啓動了一個運行了redis服務的容器。

guo@Server218:~/dockerlearn$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
cf78dfe74342        redis:3.2           "docker-entrypoint..."   2 hours ago         Up 2 hours          0.0.0.0:9999->6379/tcp   romantic_lamarr
guo@Server218:~/dockerlearn$ 

鑑於docker attach已經過時,這裏就不在新版本的docker中演示了。來看看docker exec的方式。具體的命令格式爲:docker exec -it containerid shell-env.

guo@Server218:~/dockerlearn$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
cf78dfe74342        redis:3.2           "docker-entrypoint..."   2 hours ago         Up 2 hours          0.0.0.0:9999->6379/tcp   romantic_lamarr
guo@Server218:~/dockerlearn$ docker exec -it cf78 /bin/bash
root@cf78dfe74342:/data# ps aux | grep redis
redis        1  0.0  0.4  33316  9464 ?        Ssl  04:20   0:09 redis-server *:6379          
root        33  0.0  0.0  11128  1004 ?        S+   06:58   0:00 grep redis
root@cf78dfe74342:/data# exit
exit
guo@Server218:~/dockerlearn$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
cf78dfe74342        redis:3.2           "docker-entrypoint..."   2 hours ago         Up 2 hours          0.0.0.0:9999->6379/tcp   romantic_lamarr
guo@Server218:~/dockerlearn$ 

可以看出,exec方式退出連接到的docker容器之後,容器並沒有被銷燬,而是依舊在運行着容器自身的服務。

  • 使用Ctrl+p+q的方式,可以做到退出容器而不關閉容器,這一點需要明確下,後面可能會成爲一個大坑。
  • ctrl+d 退出容器且關閉, docker ps 查看無。

Dockerfile

類似於其他的編程語言,從上往下一步步的執行,實現最終的效果。dockerfile是用來構建自己鏡像的腳本。它有固定的格式,大致形式如下:

dockerfile 格式

# dockerfile的註釋是#符號,和Python,bash等編程語言的註釋類似,在合適的地方寫註釋可以使得構建docker鏡像的邏輯更加清楚。
#第一行必須指令基於的基礎鏡像
From reponame:tagname

#維護者信息
MAINTAINER docker_maintainer  [email protected]

#鏡像的操作指令
apt-get update && apt-get upgrade

RUN apt-get update && apt-get install -y nginx
RUN echo "\ndaemon off;">>/etc/ngnix/nignix.conf

EXPOSE port1 port2

#容器啓動時執行指令
CMD /usr/sbin/ngnix
ENTRYPOINT /usr/sbin/nginx等

指令釋義

從上面我們也能看出來,一個標準的dockerfile大致有4個部分組成,分別是: 鏡像源、維護者信息、構建指令(比如安裝軟件,更新系統,設置環境等,具體不止RUN一條,還可能會有ENV, USER等)、CMD以及ENTRYPOINT。下面簡要的解釋下這些指令對應的作用。

  • FROM處於非註釋dockerfile的第一行,用來指明鏡像源的名稱,相當於我們在命令行執行了docker pull xxxx。tag名缺省爲latest
  • MAINTAINER: 指明維護者的信息,屬於可選項。但是如果想讓自己的鏡像被別人下載,就需要對應的平臺註冊下賬號,這樣方便別人的搜索。
  • 構建指令:

    • RUN應該是最常用的構建指令了。其作用就類似於在平時宿主系統中使用的bash命令,只不過RUN會在構建鏡像的時候再docker引擎中執行罷了。具體格式有兩種,shell方式和exec方式:
      • shell 方式,RUN COMMAND param1 param2
      • exec方式: RUN [‘command’, ‘param1’, ‘param2’,,,]
    • USER: 指定docker容器的使用用戶,默認是root,也可以指定其他用戶,比如對於nginx可以使用USER www-data
    • EXPOSE: 設置對外暴露的端口,可以是多個。容器運行成功後可以通過docker ps 查看到宿主機端口和容器端口的映射關係。
    • ENV: 類似於bash中的set指令,在dockerfile中定義的變量可以在後面的指令中應用。
  • CMD和ENTRYPOINT:這兩個命令都是用來啓動一個容器,但是二者稍有不同。

    • CMD: 可以有多條,但是隻有最後一條會生效,前面的將被覆蓋掉。
    • ENTRYPOINT:一個dockerfile只能有一個ENTRYPOINT命令,用來指定容器啓動的命令。相當於把鏡像改造成了一個可執行程序般來使用。

    CMD命令有一點尤其需要注意,docker run命令如果指定了參數會把CMD裏的參數覆蓋: (這裏說明一下,如:docker run -it ubuntu /bin/bash 命令的參數是指/bin/bash 而非 -it ,-it只是docker 的參數,而不是容器的參數
    – – http://cloud.51cto.com/art/201411/457338.htm

寫完一個Dockerfile之後,就可以基於其來構建自己的鏡像了。具體的命令如下:

# 爲了防止將多餘的文件構建到鏡像中,Dockerfile一般會放到一個空目錄下,然後再次目錄下執行如下命令
docker build -t tagname .

這樣,一個自定義的鏡像就完成了。可以自己嘗試下,至於publish自己的鏡像,這裏就不多做敘述了,畢竟目前平臺上已經有太多太多優秀的已經構建好的鏡像了,我們直接pull過來使用就好,自己構建的不一定有人家做的好。

實戰小例子

我本人對Python情有獨鍾,因此參考Docker構建Python鏡像來實戰下。

guo@Server218:~$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntunginx         latest              ca3292d807d3        42 hours ago        177 MB
redis               3.2                 e97b1f10d81a        2 weeks ago         99.7 MB
ubuntu              latest              452a96d81c30        3 weeks ago         79.6 MB
hello-world         latest              e38bc07ac18e        5 weeks ago         1.85 kB
guo@Server218:~$ 

通過pull方式

guo@Server218:~$ docker search python3.6
NAME                                       DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
microsoft/azure-functions-python3.6        Python 3.6 image for Azure Functions            2                    [OK]
lucascosta/serverless-python3.6            Serverless Framework with Python3.6             2                    [OK]
sky46821/centos7-python3.6                 centos7/python3.6                               1                    
arwineap/docker-ubuntu-python3.6           docker-ubuntu-python3.6                         1                    [OK]
leondomingo/ubuntu16.04-python3.6.1                                                        1                    
... ...

guo@Server218:~$ docker pull python:3.6
3.6: Pulling from library/python
3d77ce4481b1: Pull complete 
534514c83d69: Pull complete 
d562b1c3ac3f: Pull complete 
4b85e68dc01d: Pull complete 
a60ceaabb01c: Pull complete 
ba209b7a7239: Pull complete 
235ce1ab7310: Pull complete 
bd6e9cb6b441: Pull complete 
Digest: sha256:18e515f2cd7fd40c019bce12fda36b9a9c58613cf6fb8d6e58f831ef565a7b81
Status: Downloaded newer image for python:3.6
guo@Server218:~$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntunginx         latest              ca3292d807d3        43 hours ago        177 MB
python              3.6                 d69bc9d9b016        2 weeks ago         691 MB
redis               3.2                 e97b1f10d81a        2 weeks ago         99.7 MB
ubuntu              latest              452a96d81c30        3 weeks ago         79.6 MB
hello-world         latest              e38bc07ac18e        5 weeks ago         1.85 kB
guo@Server218:~$ docker run -it python:36 bash
root@8433585070a3:/# pwd
/
root@8433585070a3:/# ls
bin   dev  home  lib64  mnt  proc  run   srv  tmp  var
boot  etc  lib   media  opt  root  sbin  sys  usr

在宿主機添加一個用於執行的python文件。

guo@Server218:~/dockerlearn/docker-python36$ mkdir src
guo@Server218:~/dockerlearn/docker-python36$ cd src
guo@Server218:~/dockerlearn/docker-python36/src$ ls
guo@Server218:~/dockerlearn/docker-python36/src$ vim helloworld.py
guo@Server218:~/dockerlearn/docker-python36/src$ cat helloworld.py 
#!/usr/bin python
print("Hello Python36")
guo@Server218:~/dockerlearn/docker-python36/src$ 
guo@Server218:~/dockerlearn/docker-python36$ docker run -v $PWD/src:/home -w /home python:3.6 python helloworld.py
Hello Python36
guo@Server218:~/dockerlearn/docker-python36$ 

這句話的意思就是,將宿主機的$PWD/src目錄映射到容器的/home目錄下,然後進入容器之後通過-w參數切換到/home目錄,最後執行命令。

通過Dockerfile方式

剛纔演示了Python36,下面通過Dockerfile實現python35的鏡像構建。

guo@Server218:~/dockerlearn$ mkdir docker-python35
guo@Server218:~/dockerlearn$ ls
docker-python35  docker-python36  ubuntu-nginx.dockerfile
guo@Server218:~/dockerlearn$ cd docker-python35
guo@Server218:~/dockerlearn/docker-python35$ ls
guo@Server218:~/dockerlearn/docker-python35$ 

Dockerfile的具體內容爲從上面鏈接copy過來的,大致內容如下:

FROM buildpack-deps:jessie

# remove several traces of debian python
RUN apt-get purge -y python.*

# http://bugs.python.org/issue19846
# > At the moment, setting "LANG=C" on a Linux system *fundamentally breaks Python 3*, and that's not OK.
ENV LANG C.UTF-8

# gpg: key F73C700D: public key "Larry Hastings <[email protected]>" imported
ENV GPG_KEY 97FC712E4C024BBEA48A61ED3A5CA953F73C700D

ENV PYTHON_VERSION 3.5.1

# if this is called "PIP_VERSION", pip explodes with "ValueError: invalid truth value '<VERSION>'"
ENV PYTHON_PIP_VERSION 8.1.2

RUN set -ex \
        && curl -fSL "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz" -o python.tar.xz \
        && curl -fSL "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz.asc" -o python.tar.xz.asc \
        && export GNUPGHOME="$(mktemp -d)" \
        && gpg --keyserver ha.pool.sks-keyservers.net --recv-keys "$GPG_KEY" \
        && gpg --batch --verify python.tar.xz.asc python.tar.xz \
        && rm -r "$GNUPGHOME" python.tar.xz.asc \
        && mkdir -p /usr/src/python \
        && tar -xJC /usr/src/python --strip-components=1 -f python.tar.xz \
        && rm python.tar.xz \
        \
        && cd /usr/src/python \
        && ./configure --enable-shared --enable-unicode=ucs4 \
        && make -j$(nproc) \
        && make install \
        && ldconfig \
        && pip3 install --no-cache-dir --upgrade --ignore-installed pip==$PYTHON_PIP_VERSION \
        && find /usr/local -depth \
                \( \
                    \( -type d -a -name test -o -name tests \) \
                    -o \
                    \( -type f -a -name '*.pyc' -o -name '*.pyo' \) \
                \) -exec rm -rf '{}' + \
        && rm -rf /usr/src/python ~/.cache

# make some useful symlinks that are expected to exist
RUN cd /usr/local/bin \
        && ln -s easy_install-3.5 easy_install \
        && ln -s idle3 idle \
        && ln -s pydoc3 pydoc \
        && ln -s python3 python \
        && ln -s python3-config python-config

CMD ["python3"]

下面開始構建自己的鏡像

docker build -t mypython35 .

構建完成後,我們就可以通過docker來運行自己的容器了。同樣,這次也這麼來映射下,看看執行的結果如何。

Removing intermediate container 952d0bd576a4
Successfully built 211c8752f9d7
guo@Server218:~/dockerlearn/docker-python35$ docker run -v $PWD/../docker-python36/src:/home -w /home mypython35 python helloworld.py
Hello Python36
guo@Server218:~/dockerlearn/docker-python35$ 

這次我們使用的helloworld.py依舊是上一步的測試文件,發現可以正常得到執行結果,說明我們通過Dockerfile實現了自己的鏡像的製作了。

總結

docker很不錯,個人覺得大家都應該對此有點了解,然後就可以自己搭建集羣式的環境,或者模擬一些服務場景,這都會是很有幫助的。

關於鏡像層面,各大平臺提供的鏡像基本上能滿足我們所有的需求了,但是如果想自己試一試的話,dockerfile會是個很不錯的切入口。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章