Ubuntu上使用Docker的簡易教程
說在開頭
在天池的比賽中涉及到了docker的使用。經過多番探究,大致搞明白了使用的基本流程。這里對於關鍵部分做一個簡要總結和鏈接的跳轉。
Docker是什么、有什么優點
關於Docker官方性說明網上有很多,官方文檔也很詳細,這里不細說。
只想說在我目前的體驗中可以感受到的一點是,Docker可以打包一個完整的運行環境。這對於重現代碼的使用環境來說非常有用。使用者不需要再重新配置環境,只需要直接拉取提供的鏡像,或者是直接載入打包好的鏡像,就可以基於該鏡像創建容器運行代碼了。
所以我們需要搞清楚的是這樣幾點:
- 什么是鏡像(image)和容器(container)
- 如何獲取鏡像
- 從網絡
- 從他人
- 如何使用鏡像
- 如何使用容器
- 如何生成鏡像
- 如何分享鏡像
接下來從這幾個方面依次介紹。
首先說明我的實驗環境:
$ 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該版本鏡像。
更多詳見:
- https://docs.docker.com/engine/reference/commandline/pull/#pull-an-image-by-digest-immutable-identifier
- 通過API獲取鏡像倉庫里鏡像的標簽:https://www.jianshu.com/p/a5af4f558b0a
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的作用是表示載入容器后運行bash。docker中必須要保持一個進程的運行,要不然整個容器啟動后就會馬上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 Hub:https://docs.docker.com/get-started/part3/
- 上傳到阿里雲:https://blog.csdn.net/xiayto/article/details/104133417/
本地導出分享
這里基於指令 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
這里也有個例子:
- docker 打包本地鏡像,並到其他機器進行恢復:https://blog.csdn.net/zf3419/article/details/88533274
參考資料
- docker命令行的基本指令都在這里:https://docs.docker.com/engine/reference/commandline/docker/
- 官方給了一些指導性的文檔:https://docs.docker.com/develop/
