1. 程式人生 > 實用技巧 >Docker之Ubuntu上使用Docker的簡易教程

Docker之Ubuntu上使用Docker的簡易教程

Ubuntu上使用Docker的簡易教程

原始文件:https://www.yuque.com/lart/linux/fp6cla

說在開頭

在天池的比賽中涉及到了docker的使用。經過多番探究,大致搞明白了使用的基本流程。這裡對於關鍵部分做一個簡要總結和連結的跳轉。

Docker是什麼、有什麼優點

關於Docker官方性說明網上有很多,官方文件也很詳細,這裡不細說。

只想說在我目前的體驗中可以感受到的一點是,Docker可以打包一個完整的執行環境。這對於重現程式碼的使用環境來說非常有用。使用者不需要再重新配置環境,只需要直接拉取提供的映象,或者是直接載入打包好的映象,就可以基於該映象建立容器執行程式碼了。

所以我們需要搞清楚的是這樣幾點:

  1. 什麼是映象(image)和容器(container)
  2. 如何獲取映象
    1. 從網路
    2. 從他人
  3. 如何使用映象
  4. 如何使用容器
  5. 如何生成映象
  6. 如何分享映象

接下來從這幾個方面依次介紹。 首先說明我的實驗環境:

$ sudo docker --version
Docker version 19.03.6, build 369ce74a3c

什麼是映象(image)和容器(container)

官方說法:https://docs.docker.com/get-started/#images-and-containers

Fundamentally, a container is nothing but a running process, with some added encapsulation features applied to it in order to keep it isolated from the host and from other containers. One of the most important aspects of container isolation is that each container interacts with its own private filesystem; this filesystem is provided by a Docker image. An image includes everything needed to run an application - the code or binary, runtimes, dependencies, and any other filesystem objects required.

從根本上說,容器只不過是一個正在執行的程序,它還應用了一些附加的封裝功能,以使其與主機和其他容器保持隔離。容器隔離的最重要方面之一是每個容器都與自己的私有檔案系統進行互動;該檔案系統由Docker映象提供。映象包含執行應用程式所需的所有內容——程式碼或二進位制檔案、執行時、依賴項以及所需的任何其他檔案系統物件。

實際上在實際使用中,我們使用映象建立容器,非常像使用一個新的系統映象(這裡的映象包含了執行程式的所有內容)來安裝系統(建立容器)。只是這裡更加接近於虛擬機器。對系統本身沒有影響。

我覺的這句話說的很好:映象用來建立容器,是容器的只讀模板

但是容器和虛擬機器還不一樣,這裡引用官方文件的表述:

A container runs natively on Linux and shares the kernel of the host machine with other containers. It runs a discrete process, taking no more memory than any other executable, making it lightweight.

By contrast, a virtual machine (VM) runs a full-blown “guest” operating system with virtual access to host resources through a hypervisor. In general, VMs incur a lot of overhead beyond what is being consumed by your application logic.

容器在Linux上本地執行,並與其他容器共享主機的核心。它執行一個獨立的程序,佔用的記憶體不超過任何其他可執行檔案,這也使其更加輕量級。相比之下,虛擬機器 (VM) 執行成熟的 “訪客” 作業系統,並通過管理程式虛擬訪問主機資源。通常,虛擬機器會產生超出應用程式邏輯消耗的開銷。

所以歸根到底一句話,docker的容器更加輕便省資源。

文件中給出的容器與虛擬機器的結構差異

如何獲取映象

從前面的描述中我們可以瞭解到,使用docker的基礎是,我們得有初始的映象來作為一切的開始。主要獲取映象的方式有兩種,一種是使用他人打包好,並通過網路(主要是docker官方的docker hub和一些類似的映象託管網站)進行分享的映象,另一種則是在本地將映象儲存為本地檔案,直接使用生成的檔案進行共享。後者在網路首先環境下更加方便。

從網路

這裡主要可以藉助於兩條指令。一個是 docker pull ,另一個是 docker run

docker pull

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

顧名思義,就是從倉庫中拉取(pull)指定的映象到本機。

看它的配置項,對於可選項我們暫不需要管,我們重點關注後面的 NAME 和緊跟的兩個互斥的配置項。 對應於這裡的三種構造指令的方式,文件中給出了幾種不同的拉取方式:

  • NAME
    • docker pull ubuntu
    • 如果不指定標籤,Docker Engine會使用 :latest 作為預設標籤拉取映象。
  • NAME:TAG
    • docker pull ubuntu:14.04
    • 使用標籤時,可以再次 docker pull 這個映象,以確保具有該映象的最新版本。
    • 例如,docker pull ubuntu:14.04 將會拉取Ubuntu 14.04映象的最新版本。
  • NAME@DIGEST
    • docker pull ubuntu@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2
    • 這個為我們提供了一種指定特定版本映象的方法。
    • 為了保證後期我們僅僅使用這個版本的映象,我們可以重新通過指定DIGEST(通過檢視映象託管網站裡的映象資訊或者是之前的 pull 輸出裡的DIGEST資訊)的方式 pull 該版本映象。

更多詳見:

docker run

docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

docker run 命令首先在指定的映像上建立一個可寫的容器層,然後使用指定的命令啟動它。

這個實際上會自動從官方倉庫中下載本地沒有的映象。更多是使用會在後面建立容器的部分介紹。

$ sudo docker images
REPOSITORY                                             TAG                 IMAGE ID            CREATED             SIZE

$ sudo docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
0e03bdcc26d7: Pull complete 
Digest: sha256:49a1c8800c94df04e9658809b006fd8a686cab8028d33cfba2cc049724254202
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/get-started/

$ sudo docker images
REPOSITORY                                             TAG                 IMAGE ID            CREATED             SIZE
hello-world                                            latest              bf756fb1ae65        7 months ago        13.3kB

從他人處

關於如何儲存映象在後面介紹,其涉及到的指令為 docker save ,這裡主要講如何載入已經匯出的映象檔案。

docker load

docker load [OPTIONS]

Load an image or repository from a tar archive (even if compressed with gzip, bzip2, or xz) from a file or STDIN. It restores both images and tags.

從tar歸檔檔案 (即使使用gzip、bzip2或xz壓縮後的),從一個檔案或者STDIN中,載入映象或倉庫。它可以恢復映象和標籤。

例子可見:https://docs.docker.com/engine/reference/commandline/load/#examples

如何使用映象

單純的使用映象實際上就是圍繞指令 docker run 來的。

首先我們通過使用 docker images 來檢視本機中已經儲存的映象資訊列表。

$ sudo docker images
REPOSITORY                                             TAG                 IMAGE ID            CREATED             SIZE

通過執行 docker run 獲取映象並建立容器。

docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

run包含很多的引數和配置項,這裡放一個我用過的最長的(這裡用到了nvidia-docker:https://github.com/NVIDIA/nvidia-docker#usage): sudo docker run --rm -it --gpus all --name test -v /home/mydataroot:/tcdata:ro nvidia/cuda:10.0-base /bin/bash

這條指令做的就是:

  • 啟動Docker容器時,必須首先確定是要在後臺以 “分離” 模式還是在預設前臺模式下執行容器: -d ,這裡沒有指定 -d 則是使用預設前臺模式執行。兩種模式下,部分引數配置不同,這部分細節可以參考文件:https://docs.docker.com/engine/reference/run/#detached-vs-foreground
  • 使用映象 nvidia/cuda:10.0-base 建立容器,並對容器起一個別名 test
  • 對於該容器,開啟gpu支援,並且所有GPU都可用,但是前提你得裝好nvidia-docker。
  • --rm 表示退出容器的時候自動移除容器,在測試環境等場景下很方便,不用再手動刪除已經建立的容器了。
  • -t-i :這兩個引數的作用是,為該docker建立一個偽終端,這樣就可以進入到容器的互動模式。
  • 後面的 /bin/bash 的作用是表示載入容器後執行 bashdocker中必須要保持一個程序的執行,要不然整個容器啟動後就會馬上kill itself,這樣當你使用 docker ps 檢視啟動的容器時,就會發現你剛剛建立的那個容器並不在已啟動的容器佇列中。 這個 /bin/bash 就表示啟動容器後啟動 bash
  • -v 表示將本地的資料夾以只讀( :ro ,讀寫可以寫為 :rw ,如果不加,則預設的方式是讀寫)的方式掛載到容器中的 /tcdata 目錄中。
$ sudo docker run -it ubuntu bash
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
3ff22d22a855: Pull complete 
e7cb79d19722: Pull complete 
323d0d660b6a: Pull complete 
b7f616834fd0: Pull complete 
Digest: sha256:5d1d5407f353843ecf8b16524bc5565aa332e9e6a1297c73a92d3e754b8a636d
Status: Downloaded newer image for ubuntu:latest
root@966d5fa519a5:/# # 此時進入了基於ubuntu:latest建立的容器中。

退出容器後,檢視本機的映象資訊列表和容器資訊列表。

root@d9e6e9b9323a:/# exit
exit
$ sudo docker images
REPOSITORY                                             TAG                 IMAGE ID            CREATED             SIZE
ubuntu                                                 latest              1e4467b07108        9 days ago          73.9M
$ sudo docker ps  # 檢視正在執行的容器資訊
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
$ sudo docker ps -a  # 檢視所有容器資訊
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
966d5fa519a5        ubuntu              "bash"              38 seconds ago      Exited (0) 9 seconds ago                        relaxed_kirch

當我們想要刪除指定的映象的時候,我們可以使用 sudo docker rmi 來進行處理。

docker rmi [OPTIONS] IMAGE [IMAGE...]

後面可以跟多個映象。這裡支援三種方式:

  • 使用IMAGE ID:docker rmi fd484f19954f
  • 使用TAG: docker rmi test:latest
  • 使用DIGEST: docker rmi localhost:5000/test/busybox@sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf

如何使用容器

建立容器

從前面可以瞭解到,我們可以通過使用 docker run 建立前臺執行的容器,建立好了容器我們會面臨如何使用的問題。

進入容器

進入容器的方法都是一致的,但是這裡會面臨兩種情況,一種是已經退出的容器,另一種是執行在後臺的分離模式下的容器。

已退出的容器

$ sudo docker run -it --gpus all --name testv3 -v /home/lart/Downloads/:/data nvidia/cuda:10.0-base bash
root@efd722f0321f:/# exit
exit
(pt16) lart@god:~/Coding/RGBSOD_MS$ sudo docker ps -a
CONTAINER ID        IMAGE                   COMMAND             CREATED             STATUS                         PORTS               NAMES
efd722f0321f        nvidia/cuda:10.0-base   "bash"              19 seconds ago      Exited (0) 3 seconds ago                           testv3

可以看到,我這裡存在一個已經退出的容器。我們想要進入退出的容器首先需要啟動已經退出的容器:

(pt16) lart@god:~/Coding/RGBSOD_MS$ sudo docker start testv3
testv3
(pt16) lart@god:~/Coding/RGBSOD_MS$ sudo docker ps -a
CONTAINER ID        IMAGE                   COMMAND             CREATED             STATUS                         PORTS               NAMES
efd722f0321f        nvidia/cuda:10.0-base   "bash"              31 seconds ago      Up 1 second                                        testv3

關於重啟容器,使用 docker restart 也是可以的。為了驗證,我們先使用 docker stop 停止指定容器。再進行測試。

$ sudo docker stop testv3
testv3
$ sudo docker ps -a
CONTAINER ID        IMAGE                   COMMAND             CREATED             STATUS                         PORTS               NAMES
efd722f0321f        nvidia/cuda:10.0-base   "bash"              2 minutes ago       Exited (0) 4 seconds ago                           testv3
$ sudo docker restart testv3  # restart 不僅可以重啟關掉的容器,也可以重啟執行中的容器
testv3
$ sudo docker ps -a
CONTAINER ID        IMAGE                   COMMAND             CREATED             STATUS                         PORTS               NAMES
efd722f0321f        nvidia/cuda:10.0-base   "bash"              2 minutes ago       Up 1 second                                        testv3

後臺分離模式執行的容器

  • 對於建立分離模式的容器我們可以使用 docker run -d
  • 另外前面提到的 start 或者 restart 啟動的容器會自動以分離模式執行

進入啟動的容器

對於已經啟動的容器,我們可以使用 attach 或者 exec 進入該容器,但是更推薦後者(https://www.cnblogs.com/niuben/p/11230144.html)。

$ sudo docker ps -a
CONTAINER ID        IMAGE                   COMMAND             CREATED             STATUS                         PORTS               NAMES
efd722f0321f        nvidia/cuda:10.0-base   "bash"              16 minutes ago      Exited (0) 2 minutes ago                           testv3
$ sudo docker start testv3
testv3
$ sudo docker ps -a
CONTAINER ID        IMAGE                   COMMAND             CREATED             STATUS                         PORTS               NAMES
efd722f0321f        nvidia/cuda:10.0-base   "bash"              16 minutes ago      Up 2 seconds                                       testv3

$ sudo docker exec -it testv3 bash
root@efd722f0321f:/# exit
exit
$ sudo docker ps -a # 可以看到exec退出後不會把容器關閉
CONTAINER ID        IMAGE                   COMMAND             CREATED             STATUS                         PORTS               NAMES
efd722f0321f        nvidia/cuda:10.0-base   "bash"              17 minutes ago      Up 36 seconds                                      testv3

$ sudo docker attach testv3
root@efd722f0321f:/# exit
exit
$ sudo docker ps -a  # 可以看到attach退出後會把容器關閉
CONTAINER ID        IMAGE                   COMMAND             CREATED             STATUS                         PORTS               NAMES
efd722f0321f        nvidia/cuda:10.0-base   "bash"              18 minutes ago      Exited (0) 2 seconds ago                           testv3

退出容器

$ exit

刪除容器

基於指令 docker rm ,用來移除一個或者多個容器。

docker rm [OPTIONS] CONTAINER [CONTAINER...]

我們可以根據容器的ID或者名字來刪除對應的執行中的或者是已經停止的容器。更多的例子可見https://docs.docker.com/engine/reference/commandline/rm/#examples

停止正在執行的容器

參考:https://blog.csdn.net/Michel4Liu/article/details/80889977

  • docker stop :此方式常常被翻譯為優雅的停止容器。
    • docker stop 容器ID或容器名
    • -t :關閉容器的限時,如果超時未能關閉則用 kill 強制關閉,預設值10s,這個時間用於容器的自己儲存狀態, docker stop -t=60 容器ID或容器名
  • docker kill :直接關閉容器
    • docker kill 容器ID或容器名

從本機與容器中互相拷貝資料

docker cp :用於容器與主機之間的資料拷貝。

docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|-
docker cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH
  • The docker cp utility copies the contents of SRC_PATH to the DEST_PATH. You can copy from the container's file system to the local machine or the reverse, from the local filesystem to the container.
  • If - is specified for either the SRC_PATH or DEST_PATH, you can also stream a tar archive from STDIN or to STDOUT.
  • The CONTAINER can be a running or stopped container.
  • The SRC_PATH or DEST_PATH can be a file or directory.

由於容器內的資料與容器外的資料並不共享,所以如果我們想要向其中拷貝一些資料,可以通過這個指令來進行復制。當然,另一個比較直接的想法就是通過掛載的目錄進行檔案共享與複製。

如何生成映象

主要包含兩種方式,一種是基於構建檔案Dockerfile和 docker build 的自動構建,一種是基於 docker commit 提交對於現有容器的修改之後生成映象。

docker build

docker build [OPTIONS] PATH | URL | -
  • The docker build command builds Docker images from a Dockerfile and a "context". A build's context is the set of files located in the specified PATH or URL.
  • The build process can refer to any of the files in the context. For example, your build can use a COPY instruction to reference a file in the context.

更多細節可見https://docs.docker.com/engine/reference/commandline/build/

這裡用到了Dockerfile,這些參考資料不錯:

對於已有的Dockerfile檔案,我們可以使用如下指令生成映象:

$ docker build -t vieux/apache:2.0 .
# 使用'.'目錄下的Dockerfile檔案。注意結尾的路徑`.`,這裡給打包的映象指定了TAG
$ docker build -t whenry/fedora-jboss:latest -t whenry/fedora-jboss:v2.1 .
# 也可以指定多個TAG
$ docker build -f dockerfiles/Dockerfile.debug -t myapp_debug .
# 也可以不使用'.'目錄下的Dockerfile檔案,而是使用-f指定檔案

打包完之後,我們就可以在 docker images 中看到新增的映象了。

docker commit

docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
  • It can be useful to commit a container's file changes or settings into a new image. This allows you to debug a container by running an interactive shell, or to export a working dataset to another server.
  • Generally, it is better to use Dockerfiles to manage your images in a documented and maintainable way. Read more about valid image names and tags.
  • The commit operation will not include any data contained in volumes mounted inside the container.
  • By default, the container being committed and its processes will be paused while the image is committed. This reduces the likelihood of encountering data corruption during the process of creating the commit. If this behavior is undesired, set the --pause option to false.

一般使用指定容器的ID就可以進行提交了。

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS              NAMES
c3f279d17e0a        ubuntu:12.04        /bin/bash           7 days ago          Up 25 hours                            desperate_dubinsky
197387f1b436        ubuntu:12.04        /bin/bash           7 days ago          Up 25 hours                            focused_hamilton
$ docker commit c3f279d17e0a  svendowideit/testimage:version3
f5283438590d
$ docker images
REPOSITORY                        TAG                 ID                  CREATED             SIZE
svendowideit/testimage            version3            f5283438590d        16 seconds ago      335.7 MB

如何分享映象

上傳到線上儲存庫

本地匯出分享

這裡基於指令 docker save

docker save [OPTIONS] IMAGE [IMAGE...]

Produces a tarred repository to the standard output stream. Contains all parent layers, and all tags + versions, or specified repo:tag , for each argument provided.

具體的例子可見:https://docs.docker.com/engine/reference/commandline/save/#examples

$ sudo docker ps -a
CONTAINER ID        IMAGE                   COMMAND             CREATED             STATUS                         PORTS               NAMES
153999a1dfb2        hello-world             "/hello"            2 hours ago         Exited (0) 2 hours ago                             naughty_euler
$ sudo docker images
REPOSITORY                                             TAG                 IMAGE ID            CREATED             SIZE
hello-world                                            latest              bf756fb1ae65        7 months ago        13.3kB
$ sudo docker save -o hello-world-latest.tar hello-world:latest
$ ls
hello-world-latest.tar

$ sudo docker rmi hello-world:latest
Error response from daemon: conflict: unable to remove repository reference "hello-world:latest" (must force) - container 153999a1dfb2 is using its referenced image bf756fb1ae65
$ sudo docker rmi -f hello-world:latest  # 強制刪除映象
Untagged: hello-world:latest
Untagged: hello-world@sha256:49a1c8800c94df04e9658809b006fd8a686cab8028d33cfba2cc049724254202
Deleted: sha256:bf756fb1ae65adf866bd8c456593cd24beb6a0a061dedf42b26a993176745f6b

$ sudo docker ps -a  # 課件,強制刪除映象後,原始關聯的容器並不會被刪除
CONTAINER ID        IMAGE                   COMMAND             CREATED             STATUS                   PORTS               NAMES
153999a1dfb2        bf756fb1ae65            "/hello"            5 hours ago         Exited (0) 5 hours ago                       naughty_euler
$ sudo docker images
REPOSITORY                                             TAG                 IMAGE ID            CREATED             SIZE

$ sudo docker load -i hello-world-latest.tar 
Loaded image: hello-world:latest

$ sudo docker images
REPOSITORY                                             TAG                 IMAGE ID            CREATED             SIZE
hello-world                                            latest              bf756fb1ae65        7 months ago        13.3kB
$ sudo docker ps -a  # 可見,當重新載入對應的映象時,這裡的IMAGE又對應了回來
CONTAINER ID        IMAGE                   COMMAND             CREATED             STATUS                   PORTS               NAMES
153999a1dfb2        hello-world             "/hello"            5 hours ago         Exited (0) 5 hours ago                       naughty_euler

這裡也有個例子:

參考資料