【學習筆記】Docker 安裝部署及常用操作演練


前言

記錄 Docker 的安裝部署,以及一些常用操作,主要目的在於鞏固學習,同時也希望對初學者起到一此借鑒作用。

PS:本次演練的系統環境為 CentOS 7,Docker 版本為 20.10.12, build e91ed57。

本文內容參考:Docker 教程 | 菜鳥教程 (runoob.com)

Docker 簡介

1、Docker簡介

Docker 是一個開源的應用容器引擎,基於Go 語言並遵從 Apache2.0 協議開源。

Docker 可以讓開發者打包他們的應用以及依賴包到一個輕量級、可移植的容器中,然后發布到任何流行的 Linux 機器上,也可以實現虛擬化。

容器是完全使用沙箱機制,相互之間不會有任何接口(類似 iPhone 的 app)更重要的是容器性能開銷極低。

Docker支持將軟件編譯成鏡像;在鏡像中支持各種軟件配置好並發布,其他使用者可以直接使用配置好的鏡像。運行中的鏡像稱之為容器,容器啟動速度很快。類似於封裝好的Windows系統,通過U盤直接安裝即可,不需要進行系統配置軟件。

Docker的應用場景:

  • Web應用的自動化打包發布;
  • 自動化測試和持續集成、發布;
  • 在服務型環境中部署調整數據庫或其他的后台應用;
  • 從頭編譯或者擴展現有的 OpenShift 或 Cloud Foundry 平台來搭建自己的 PaaS 環境。

Docker的優點:

  • 快速、一致性的交付應用程序
  • 響應式部署和擴展
  • 充分利用虛擬機資源

2、Docker核心概念

Docker主機(Host):安裝了Docker程序的機器(Docker直接安裝在操作系統中)

Docker客戶端(Client):連接Docker主機進行操作;

Docker容器(Container):鏡像啟動后的實例,獨立運行的一個或一組應用;

Docker鏡像(Image):打包好的軟件,用於創建Docker容器的模板;

Docker倉庫(Respository):用於保存打包好的軟件鏡像;

關系示意圖:

img

Docker的基本使用方式:

① 在機器中安裝Docker;

② 在Docker倉庫中尋找這個軟件對應的鏡像;

③ 使用Docker運行鏡像,生成一個Docker容器;

④ 容器的啟動或停止相當於對軟件的啟動和停止。

Docker 安裝部署

1、安裝環境

操作系統:CentOS 7

系統版本:3.10.0-1160.el7.x86_64

備注說明:本次演練基於 VMware® Workstation 16 Pro 安裝 CentOS 7 虛擬機,安裝時選擇最小安裝模式。

英語水平有限,為了便於查看,在 CentOS 安裝過程中直接把系統語言設置為簡體中文,系統時區設置為“亞洲”-“上海”。

CentOS 7 系統內核版本高於 3.10,可以使用以下命令查看:

uname -r

以下為執行結果,可以看出顯示內核版本為:3.10.0-1160.el7.x86_64

[root@localhost ~]# uname -r
3.10.0-1160.el7.x86_64
[root@localhost ~]#

2、配置 yum

安裝 docker ce 即社區免費版,須先安裝必要的軟件包。安裝 yum-utils,它提供一個 yum-config-manager 單元。同時安裝的 device-mapper-persistent-data 和 lvm2 用於儲存設備映射(devicemapper)。

sudo yum update
sudo yum install -y yum-utils device-mapper-persistent-data lvm2

# 可以按以下方式同時執行多條命令
# sudo yum update && yum install -y yum-utils device-mapper-persistent-data lvm2

以下為執行結果截圖。由於剛剛我已執行過該命令,所以直接顯示為“已安裝”。在安裝過程中可能會有部分交互操作,查看提示,輸入“y”即可。
img

直接使用官方倉庫進行 Docker 安裝速度會有點慢,所以我們緊接着配置一個穩定(stable)的倉庫,倉庫配置會保存到 /etc/yum.repos.d/docker-ce.repo 文件中。此處我們使用阿里雲。

sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

結果如下:
img

執行以下命令,通過 Vim 查看鏡像倉庫配置:

vim /etc/yum.repos.d/docker-ce.repo

# 也可以使用 cat 命令查看
# cat /etc/yum.repos.d/docker-ce.repo

文件內容如下:

[docker-ce-stable]
name=Docker CE Stable - $basearch
baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/$releasever/$basearch/stable
enabled=1
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg

[docker-ce-stable-debuginfo]
name=Docker CE Stable - Debuginfo $basearch
baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/$releasever/debug-$basearch/stable
enabled=0
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg

[docker-ce-stable-source]
name=Docker CE Stable - Sources
baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/$releasever/source/stable
enabled=0
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg

[docker-ce-test]
name=Docker CE Test - $basearch
baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/$releasever/$basearch/test
enabled=0
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg

[docker-ce-test-debuginfo]
name=Docker CE Test - Debuginfo $basearch
baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/$releasever/debug-$basearch/test
enabled=0
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg

[docker-ce-test-source]
name=Docker CE Test - Sources
baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/$releasever/source/test
enabled=0
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg

[docker-ce-nightly]
name=Docker CE Nightly - $basearch
baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/$releasever/$basearch/nightly
enabled=0
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg

[docker-ce-nightly-debuginfo]
name=Docker CE Nightly - Debuginfo $basearch
baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/$releasever/debug-$basearch/nightly
enabled=0
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg

[docker-ce-nightly-source]
name=Docker CE Nightly - Sources
baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/$releasever/source/nightly
enabled=0
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg

以上內容僅僅是了解一下,關閉文檔后我們繼續下面操作。

3、安裝 Docker

查看倉庫版本:

yum list docker-ce --showduplicates | sort -r

# 參數說明:
# 直接使用 yum list docker-ce 命令僅顯示最新版本。
# 添加 --showduplicates 參數后會顯示所有鏡像版本。
# 添加 | sort -r 管道參數后,列表倒序顯示,最新的版本在上面展示。

執行效果如下。可以看出,當前 Docker 的最新版本是 3:20.10.12-3.el7,鏡像名稱為 docker-ce.x86_64 。
img

執行以下命令進行 Docker 安裝。默認安裝最新版本,同時會自動安裝相關依賴。安裝過程中會有提示,選“y”確認即可。

yum install docker-ce

安裝過程如下圖所示:
img

img

通過以下命令查看 Docker 運行狀態

systemctl status docker

查看輸出結果:

[root@localhost ~]# systemctl status docker
● docker.service - Docker Application Container Engine
   Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; vendor preset: disabled)
   Active: inactive (dead) # 當前狀態為停止狀態
     Docs: https://docs.docker.com
[root@localhost ~]# 

4、啟動 Docker

  • 執行命令啟動 Docker
systemctl start docker

# 可同步執行以下命令配置開機自啟動
# systemctl start docker && systemctl enable docker
  • 配置 Docker 開機自啟動
systemctl enable docker

輸出結果如下所示:

[root@localhost ~]# systemctl enable docker
Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.
[root@localhost ~]#

5、驗證 Docker

  • 查看 Docker 安裝的版本

    docker --version
    

    結果如下圖:
    img

  • 查看 Docker 運行狀態

    systemctl status docker
    

    結果如下圖:
    img

  • 查看 Docker 配置信息

    docker info
    

    結果如下圖:
    img

6、卸載 Docker

查看已安裝的組件:

yum list installed | grep docker

結果如下圖所示:
img

刪除安裝包:

yum remove <移除需要卸載的組件>

# yum remove docker-ce

刪除鏡像、容器、配置文件等內容:

rm -rf /var/lib/docker

PS:以上為 Docker 在 CentOS 7 上進行安裝部署的一個簡演練。供初學者借鑒,如有不當之處,也請大家指正。

Docker 鏡像加速

國內從 DockerHub 拉取鏡像有時會比較困難,可以配置鏡像加速器。Docker 官方和國內很多雲服務商都提供了國內加速器服務,例如:阿里雲:https://tia33io9.mirror.aliyuncs.com 、科大鏡像:https://docker.mirrors.ustc.edu.cn/、道客雲:http://f1361db2.m.daocloud.io、網易:https://hub-mirror.c.163.com/、七牛雲:https://reg-mirror.qiniu.com等。

Docker 官方加速器 https://registry.docker-cn.com ,現在好像已經不能使用了,我們可以多添加幾個國內的鏡像,如果有不能使用的,會切換到可以使用個的鏡像來拉取。

當配置某一個加速器地址之后,若發現拉取不到鏡像,請切換到另一個加速器地址。國內各大雲服務商均提供了 Docker 鏡像加速服務,建議根據運行 Docker 的雲平台選擇對應的鏡像加速服務。

打開終端,使用 vim 打開 /etc/docker/daemon.json 文件,寫入以下內容(如果文件不存在請新建該文件):

 vim /etc/docker/daemon.json
{
    "registry-mirrors": [
        "https://tia33io9.mirror.aliyuncs.com",
        "https://docker.mirrors.ustc.edu.cn",
        "http://f1361db2.m.daocloud.io",
        "https://hub-mirror.c.163.com",
        "https://reg-mirror.qiniu.com",
        "https://registry.docker-cn.com"
    ],
    "features": {
        "buildkit": true
    }
}

之后重新啟動服務:

 sudo systemctl daemon-reload && sudo systemctl restart docker

Docker 簡單示例

Docker 允許你在容器內運行應用程序, 使用 docker run 命令來在容器內運行一個應用程序。此處以 Nginx 服務部署做一個演練。

 docker run --name my-nginx -d -p 8888:80 -e TZ=Asia/Shanghai nginx

執行結果見下圖:
img

PS:由於之前沒有拉取過 Nginx 的鏡像,所以會先自動下載 Nginx 鏡像。拉取后自動運行容器。通過 docker ps 命令可以查看運行中的容器列表。

各個參數解析:

  • docker: Docker 的二進制執行文件。
  • run: 與前面的 docker 組合來運行一個容器。
  • --name my-nginx:指定運行的容器名稱為 my-nginx
  • -d :后台運行。在大部分的場景下,我們希望 docker 的服務是在后台運行的,我們可以過 -d 指定容器的運行模式。注:加了 -d 參數默認不會進入容器,想要進入容器需要使用指令 docker exec(下面會介紹到)。
  • -p 8888:80:映射端口。開放主機 8888 端口,映射到容器的 80 端口。
  • -e TZ=Asia/Shanghai:環境變量。設置容器中運行程序的時區為 亞洲-上海。(我個人感覺這個設置有時很有必要,如果服務器時區與訪問客戶端不一致時,會出問題的)
  • nginx:指定要運行的鏡像,Docker 首先從本地主機上查找鏡像是否存在,如果不存在,Docker 就會從鏡像倉庫 Docker Hub 下載公共鏡像。

執行 docker ps 命令,會把當前運行的容器給列出來。結果如上圖所示。

字段 名稱 結果
CONTAINER ID 容器ID b7498b9d4ba1
IMAGE 鏡像名稱 nginx
COMMAND 執行命令 "/docker-entrypoint.…"
CREATED 創建時間 2 minutes ago
STATUS 運行狀態 Up 2 minutes
PORTS 端口映射 0.0.0.0:8888->80/tcp, :::8888->80/tcp
NAMES 容器名稱 my-nginx

CentOS虛擬機的 局域網 IP為 192.168.1.30,通過瀏覽器訪問 http://192.168.1.30:8888 ,出現以下畫面,說明 nginx 服務器已正常運行。

img

Docker 鏡像使用

當運行容器時,使用的鏡像如果在本地中不存在,docker 就會自動從 docker 鏡像倉庫中下載,默認是從 Docker Hub 公共鏡像源下載。

1、列出鏡像

我們可以使用 docker images 來列出本地主機上的鏡像。

 docker images

# docker images 命令默認不顯示中間鏡像,如需查看全部鏡像,請添加 -a 參數,使用 docker images -a 命令進行查詢。

img

各列字段說明:

  • REPOSITORY:表示鏡像的倉庫源
  • TAG:鏡像的標簽
  • IMAGE ID:鏡像ID
  • CREATED:鏡像創建時間
  • SIZE:鏡像大小

同一倉庫源可以有多個 TAG,代表這個倉庫源的不同個版本,如 ubuntu 倉庫源里,有 15.10、14.04 等多個不同的版本,我們使用 REPOSITORY:TAG 來定義不同的鏡像。

所以,我們如果要使用版本為15.10的ubuntu系統鏡像來運行容器時,命令如下:

docker run -it ubuntu:15.10 /bin/bash 

如果你不指定一個鏡像的版本標簽,例如你只使用 ubuntu,docker 將默認使用 ubuntu:latest 鏡像。

PS:如果想要查詢某個鏡像之前的版本(Tag),需要到 Docker Hub 官網 https://hub.docker.com/ 進行查詢獲取。

2、查找鏡像

我們可以從 Docker Hub 網站來搜索鏡像,Docker Hub 網址為: https://hub.docker.com/

我們也可以使用 docker search 命令來搜索鏡像。比如我們需要一個 httpd 的鏡像來作為我們的 web 服務。我們可以通過 docker search 命令搜索 httpd 來尋找適合我們的鏡像。

 docker search nginx

img

NAME: 鏡像倉庫源的名稱

DESCRIPTION: 鏡像的描述

OFFICIAL: 是否 docker 官方發布

STARS: 類似 Github 里面的 star,表示點贊、喜歡的意思。

AUTOMATED: 自動構建。

3、拖取鏡像

當我們在本地主機上使用一個不存在的鏡像時 Docker 就會自動下載這個鏡像。如果我們想預先下載這個鏡像,我們可以使用 docker pull 命令來下載它。

# 下載 ubuntu 鏡像到本機
 docker run ubuntu

下載完成后,我們就可以使用這個鏡像了。

# 運行並進入容器,執行相應命令
docker run -it ubuntu /bin/bash

PS:我運行到這里竟然失敗了!出現以下提示:

WARNING: IPv4 forwarding is disabled. Networking will not work.

網上搜索了一下,通過以下辦法解決 “IPv4 forwarding is disabled” 的問題。

第一步:在宿主機上執行 echo "net.ipv4.ip_forward = 1" >>/usr/lib/sysctl.d/00-system.conf

# 執行命令
echo "net.ipv4.ip_forward = 1" >>/usr/lib/sysctl.d/00-system.conf

# 查看文件
cat /usr/lib/sysctl.d/00-system.conf

# 以下為文件內容,可以看到 net.ipv4.ip_forward = 1 的設置已追加到文件末尾。

# Kernel sysctl configuration file
#
# For binary values, 0 is disabled, 1 is enabled.  See sysctl(8) and
# sysctl.conf(5) for more details.

# Disable netfilter on bridges.
net.bridge.bridge-nf-call-ip6tables = 0
net.bridge.bridge-nf-call-iptables = 0
net.bridge.bridge-nf-call-arptables = 0
net.ipv4.ip_forward = 1

第二步:重啟network和docker服務

 systemctl restart network && systemctl restart docker

第三步:驗證是否成功

 docker run -it ubuntu /bin/bash

完美解決。

4、刪除鏡像

鏡像刪除使用 **docker rmi ** 或 docker image rm 命令:

 docker rmi ubuntu:14.04

img

刪除指定鏡像,也可以使用鏡像ID:

[root@localhost ~]# docker image rm 9b9cb95443b5
Untagged: ubuntu:15.10
Untagged: ubuntu@sha256:02521a2d079595241c6793b2044f02eecf294034f31d6e235ac4b2b54ffc41f3
Deleted: sha256:9b9cb95443b5f846cd3c8cfa3f64e63b6ba68de2618a08875a119c81a8f96698
Deleted: sha256:b616585738eaf78ff7d86c7526caf7c91a35bc4028ef63204e5bfee82f7494b5
Deleted: sha256:dee1316f97acc7e1a5088b02fbc2b3078e0bfa038dd904b8072e2de5656e7bb8
Deleted: sha256:e7d9ae1a69c53c9fefa1aef34348be5a5dbf2fe79e7dd647b3d4f4e927587ebc
Deleted: sha256:f121afdbbd5dd49d4a88c402b1a1a4dca39c9ae75ed7f80a29ffd9739fc680a7
[root@localhost ~]# #這里再列表所有鏡像,可以看到 ubuntu:15.10 已不見了。
[root@localhost ~]# docker images
REPOSITORY   TAG       IMAGE ID       CREATED        SIZE
nginx        latest    605c77e624dd   8 days ago     141MB
ubuntu       latest    ba6acccedd29   2 months ago   72.8MB
[root@localhost ~]#

5、創建鏡像

當我們從 docker 鏡像倉庫中下載的鏡像不能滿足我們的需求時,我們可以通過以下兩種方式對鏡像進行更改。

  • 1、從已經創建的容器中更新鏡像,並且提交這個鏡像
  • 2、使用 Dockerfile 指令來創建一個新的鏡像

5.1 更新鏡像

更新鏡像之前,我們需要使用鏡像來創建一個容器。

  docker run -it ubuntu:latest /bin/bash

# 注意哦,通過前面顯示的登錄賬號名稱,可以看出,目前已是進入了 ubuntu 容器中了。

在運行的容器內使用 apt-get update 命令進行更新。

在完成操作之后,輸入 exit 命令來退出這個容器。

img

此時 ID 為 dd88dacb1af7 的容器,是按我們的需求更改的容器。我們可以通過命令 docker commit 來提交容器副本。

[root@localhost ~]# docker commit -m="This has been updated." -a="jack" dd88dacb1af7 jack/ubuntu:v2
sha256:d1c2e19bdcdcb5f3ffb4c5b1c795e938defa9c316588ec49603a817c472f303b
[root@localhost ~]#

各個參數說明:

  • -m: 提交的描述信息
  • -a: 指定鏡像作者
  • dd88dacb1af7:容器 ID
  • jack/ubuntu:v2: 指定要創建的目標鏡像名

我們可以使用 docker images 命令來查看我們的新鏡像 jack/ubuntu:v2

[root@localhost ~]# docker images
REPOSITORY    TAG       IMAGE ID       CREATED              SIZE
jack/ubuntu   v2        d1c2e19bdcdc   About a minute ago   105MB
nginx         latest    605c77e624dd   8 days ago           141MB
ubuntu        latest    ba6acccedd29   2 months ago         72.8MB

5.2 構建鏡像

使用VS2022,基於.NET 6 ,創建一個控制台程序進行鏡像發布。最簡單的 Hello World 輸出。

  • 選擇項目模板

img

  • 設置項目名稱,就叫 HelloWorldDemo 吧。(選擇將解決方案和項目放在同一目錄)

img

  • 選擇運行時版本為 .NET 6.0 (長期支持),然后點擊“創建”按鈕。

img

項目創建完成,整個程序目前僅一句代碼:

Console.WriteLine("Hello, World!");

img

  • 點擊右側解決方案資源管理器,在項目名稱上面右鍵,選擇 “添加” -- “Docker 支持 …”,會彈出一個“Docker 文件選項”窗口,選擇 “Linux” 。

img

  • 點擊確定后,會在項目下生成一個 Dockerfile 文件。

img

Dockerfile 文件代碼如下:

#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.

FROM mcr.microsoft.com/dotnet/runtime:6.0 AS base
WORKDIR /app

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["HelloWorldDemo.csproj", "."]
RUN dotnet restore "./HelloWorldDemo.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "HelloWorldDemo.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "HelloWorldDemo.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "HelloWorldDemo.dll"]

每一個指令都會在鏡像上創建一個新的層,每一個指令的前綴都必須是大寫的。

第一條FROM,指定使用哪個鏡像源

RUN 指令告訴docker 在鏡像內執行命令,安裝了什么。。。

后面,我們會使用 Dockerfile 文件,通過 docker build 命令來構建一個鏡像。

  • 選擇控制台輸出方式,本地運行調試。輸出 “Hello World” ,說明程序運行正常。

img

OK ,到了這一步,我們的程序已准備好了。下面就選擇把項目整體打包到 Linux,在服務器上進行編輯打包,鏡像構建。

  • 項目編譯通過后,先清理一下解決方案。
  • 打開項目所在文件夾,把一些無用的文件刪除掉。

img

  • 清理掉無用文件夾,直接把項目文件夾上傳服務器。如下圖所示,關鍵點就在於本次上傳的文件中包含了 Dockerfile 文件。

img

  • 執行鏡像打包 docker build 命令: ( 注意哦,命令最后面有一個 “.” 符號 )
 docker build -t my-hello-world .

參數說明:

  • -t :指定要創建的目標鏡像名
  • . :Dockerfile 文件所在目錄,可以指定Dockerfile 的絕對路徑
  • 開台下載中間鏡像,並進行編輯。

img

最終完整輸出如下:


[root@localhost HelloWorldDemo]# docker build -t my-hello-world .
Sending build context to Docker daemon  7.168kB
Step 1/15 : FROM mcr.microsoft.com/dotnet/runtime:6.0 AS base
6.0: Pulling from dotnet/runtime
a2abf6c4d29d: Pull complete
08af7dd3c640: Pull complete
742307799914: Pull complete
a260dbcd03fc: Pull complete
Digest: sha256:315b0ab91f3abf2b29bc91963d86be6dd69abce3fe272306ed4ef8a5a7a190c0
Status: Downloaded newer image for mcr.microsoft.com/dotnet/runtime:6.0
 ---> 8a2ce7cb4b01
Step 2/15 : WORKDIR /app
 ---> Running in 9c79c4e71115
Removing intermediate container 9c79c4e71115
 ---> f6738ccc8759
Step 3/15 : FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
6.0: Pulling from dotnet/sdk
a2abf6c4d29d: Already exists
08af7dd3c640: Already exists
742307799914: Already exists
a260dbcd03fc: Already exists
96c3c696f47e: Pull complete
d81364490ceb: Pull complete
3e56f7c4d95f: Pull complete
9939dbdaf4a7: Pull complete
Digest: sha256:a7af03bdead8976d4e3715452fc985164db56840691941996202cea411953452
Status: Downloaded newer image for mcr.microsoft.com/dotnet/sdk:6.0
 ---> e86d68dca8c7
Step 4/15 : WORKDIR /src
 ---> Running in 2442842227a1
Removing intermediate container 2442842227a1
 ---> d11c08ee15dd
Step 5/15 : COPY ["HelloWorldDemo.csproj", "."]
 ---> cf0d49961aad
Step 6/15 : RUN dotnet restore "./HelloWorldDemo.csproj"
 ---> Running in b3962d0da619
  Determining projects to restore...
  Restored /src/HelloWorldDemo.csproj (in 2.16 sec).
Removing intermediate container b3962d0da619
 ---> 649ed5b1ae54
Step 7/15 : COPY . .
 ---> e43f4a0cc84f
Step 8/15 : WORKDIR "/src/."
 ---> Running in 95ee5b391f75
Removing intermediate container 95ee5b391f75
 ---> dd6909ce462f
Step 9/15 : RUN dotnet build "HelloWorldDemo.csproj" -c Release -o /app/build
 ---> Running in e05f29a6ca5b
Microsoft (R) Build Engine version 17.0.0+c9eb9dd64 for .NET
Copyright (C) Microsoft Corporation. All rights reserved.

  Determining projects to restore...
  All projects are up-to-date for restore.
  HelloWorldDemo -> /app/build/HelloWorldDemo.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:06.11
Removing intermediate container e05f29a6ca5b
 ---> 266ccf4a1f98
Step 10/15 : FROM build AS publish
 ---> 266ccf4a1f98
Step 11/15 : RUN dotnet publish "HelloWorldDemo.csproj" -c Release -o /app/publish
 ---> Running in 1e914748682c
Microsoft (R) Build Engine version 17.0.0+c9eb9dd64 for .NET
Copyright (C) Microsoft Corporation. All rights reserved.

  Determining projects to restore...
  All projects are up-to-date for restore.
  HelloWorldDemo -> /src/bin/Release/net6.0/HelloWorldDemo.dll
  HelloWorldDemo -> /app/publish/
Removing intermediate container 1e914748682c
 ---> 018f9624857c
Step 12/15 : FROM base AS final
 ---> f6738ccc8759
Step 13/15 : WORKDIR /app
 ---> Running in 8764e2145751
Removing intermediate container 8764e2145751
 ---> d7ec7560e4f9
Step 14/15 : COPY --from=publish /app/publish .
 ---> 637fe040bfdc
Step 15/15 : ENTRYPOINT ["dotnet", "HelloWorldDemo.dll"]
 ---> Running in be3764a6bee1
Removing intermediate container be3764a6bee1
 ---> 1add28a21d49
Successfully built 1add28a21d49
Successfully tagged my-hello-world:latest
[root@localhost HelloWorldDemo]#

  • 使用docker images 查看創建的鏡像已經在列表中存在,鏡像ID為1add28a21d49,名稱為 my-hello-world

img

  • 最后,我們可以使用容器來運行鏡像。可以看出,Hello,World !已正常輸出。( •̀ ω •́ )✧
 docker run my-hello-world

img

6、設置標簽

我們可以使用 docker tag 命令,為鏡像添加一個新的標簽。

 docker tag 1add28a21d49 my-hello-world:V1.0
 # 參數說明:
 # docker tag <鏡像ID> <鏡像源名> <新的標簽名>

使用 docker images 命令可以看到,ID為 1add28a21d49 的鏡像多一個標簽 V1.0。


[root@localhost HelloWorldDemo]# docker images
REPOSITORY                         TAG       IMAGE ID       CREATED          SIZE
my-hello-world                     V1.0      1add28a21d49   15 minutes ago   188MB
my-hello-world                     latest    1add28a21d49   15 minutes ago   188MB
<none>                             <none>    018f9624857c   15 minutes ago   726MB
mcr.microsoft.com/dotnet/sdk       6.0       e86d68dca8c7   2 weeks ago      716MB
mcr.microsoft.com/dotnet/runtime   6.0       8a2ce7cb4b01   2 weeks ago      188MB
[root@localhost HelloWorldDemo]#

PS:我個人理解,打標簽就是把原甩的鏡像復制一份,加個標簽顯示而已。

Docker 容器使用

1、幫助手冊

docker 客戶端非常簡單 ,我們可以直接輸入 docker 命令來查看到 Docker 客戶端的所有命令選項。

[root@localhost ~]# docker

這個就不放圖了,可以自己去試一下。可以通過命令 docker command --help 更深入的了解指定的 Docker 命令使用方法。

例如我們要查看 docker run 指令的具體使用方法:

[root@localhost ~]# docker run --help

img

2、容器列表

PS:如果我們本地沒有鏡像,我們可以使用 docker pull 命令來載入鏡像:

 docker pull nginx
# 查看正在運行的容器列表
 docker ps 

# 所有容器列表(包含存活和退出容器)
 docker ps –a 

2、啟動容器

 docker run -d -p 9999:80 --name my-nginx nginx

參數說明:(見前面簡單示例內容)

#啟動已存在的容器
 docker start <容器 ID/Name>

#重啟容器
 docker restart 容器id1 [容器id2] [...] 

#啟動所有容器
 docker start $(docker ps -aq)

#查看容器的進程PID
 doker top <name | ID>

3、啟動已停止運行的容器

查看所有的容器命令如下:

 docker ps -a

使用 docker start 啟動一個已停止的容器:

 docker start 70994ac442fa

img

4、后台運行

在大部分的場景下,我們希望 docker 的服務是在后台運行的,我們可以過 -d 指定容器的運行模式。

 docker run -d -p 9999:80 --name my-nginx nginx

注:加了 -d 參數默認不會進入容器,想要進入容器需要使用指令 docker exec

5、停止容器

停止容器的命令如下:

 docker stop <容器 ID>

停止的容器可以通過 docker restart 重啟:

 docker restart <容器 ID>

6、進入容器

在使用 -d 參數時,容器啟動后會進入后台。此時想要進入容器,可以通過以下指令進入:

  • docker attach
  • docker exec:推薦大家使用 docker exec 命令,因為此退出容器終端,不會導致容器的停止。
# 進入容器
 docker exec -it 24054bd26a66 /bin/bash

# 進入后就可以執行相應的命令了 ……

# 退出容器
 exit

7、刪除容器

刪除容器使用 docker rm 命令:

# 正常情況下,我們只能刪除已停止的容器,添加  -f  參數,強制執行,不再考慮容器是否正在運行。
[root@localhost HelloWorldDemo]# docker rm -f 70994ac442fa
70994ac442fa
[root@localhost HelloWorldDemo]# 

8、查看日志

docker logs [ID或者名字] 可以查看容器內部的標准輸出。

 docker logs <Id | name>
[root@localhost HelloWorldDemo]# docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS          PORTS                                   NAMES
24054bd26a66   nginx     "/docker-entrypoint.…"   18 minutes ago   Up 18 minutes   0.0.0.0:9999->80/tcp, :::9999->80/tcp   my-nginx
[root@localhost HelloWorldDemo]# docker logs 24054bd26a66
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2022/01/09 07:29:48 [notice] 1#1: using the "epoll" event method
2022/01/09 07:29:48 [notice] 1#1: nginx/1.21.5
2022/01/09 07:29:48 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
2022/01/09 07:29:48 [notice] 1#1: OS: Linux 3.10.0-1160.49.1.el7.x86_64
2022/01/09 07:29:48 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2022/01/09 07:29:48 [notice] 1#1: start worker processes
2022/01/09 07:29:48 [notice] 1#1: start worker process 32
2022/01/09 07:29:48 [notice] 1#1: start worker process 33
2022/01/09 07:29:48 [notice] 1#1: start worker process 34
2022/01/09 07:29:48 [notice] 1#1: start worker process 35
[root@localhost HelloWorldDemo]#

9、運行狀態

通過 docker stats <容器名稱|ID> 命令可以查看容器的運行狀態(會一直監控)。

[root@localhost HelloWorldDemo]# docker stats my-nginx
#================================
CONTAINER ID   NAME       CPU %     MEM USAGE / LIMIT     MEM %     NET I/O     BLOCK I/O     PIDS
24054bd26a66   my-nginx   0.01%     3.074MiB / 1.777GiB   0.17%     656B / 0B   0B / 14.3kB   5
CONTAINER ID   NAME       CPU %     MEM USAGE / LIMIT     MEM %     NET I/O     BLOCK I/O     PIDS
24054bd26a66   my-nginx   0.01%     3.074MiB / 1.777GiB   0.17%     656B / 0B   0B / 14.3kB   5
CONTAINER ID   NAME       CPU %     MEM USAGE / LIMIT     MEM %     NET I/O     BLOCK I/O     PIDS
24054bd26a66   my-nginx   0.00%     3.074MiB / 1.777GiB   0.17%     656B / 0B   0B / 14.3kB   5
…… ……

10、查看配置

使用 docker inspect 來查看 Docker 的底層信息。它會返回一個 JSON 文件記錄着 Docker 容器的配置和狀態信息。

 docker inspect my-nginx #這里可以是容器名稱,最好是使用容器ID

11、一張圖總結 Docker 命令

img

12、復制文件到容器

 docker cp sentinel.conf redis-master:/usr/local/redis
#把文件 sentinel.conf 拷貝到容器 redis-master 的 /usr/local/redis 目錄下面。

 docker cp redis-master:/usr/local/redis/sentinel.conf ./
#把容器 redis-master 中的文件/usr/local/redis/sentinel.con拷貝到當前操作目錄下在。

#注意:docker cp 命令只可以宿主機上運行

Docker 數據存儲

1、什么是數據卷

在Docker中,容器的數據讀寫默認發生在容器的存儲層,當容器被刪除時其上的數據將會丟失。如果想實現數據的持久化,就需要將容器和宿主機建立聯系(將數據從宿主機掛載到容器內),通俗的說,數據卷就是在容器和宿主機之間實現數據共享。

2、Docker支持的三種數據掛載方式

Docker 提供了三種不同的方式將數據從宿主機掛載到容器中:volume、bind mounts、tmpfs mounts

volume:Docker管理宿主機文件系統的一部分(/var/lib/docker/volumes)

bind mounts:可以存儲在宿主機系統的任意位置

tmpfs mounts:掛載存儲在宿主機系統的內存中,不會寫入宿主機的文件系統

img

3、Volume(普通數據卷)

創建volume數據卷:

 docker volume create for_nginx

查看當前所有數據卷信息:

 docker volume ls

會顯示一大堆名字為很長字符的數據卷為匿名數據卷,是因為之前創建容器的時候沒有手動創建數據卷進行了文件掛載,Docker就會自動創建匿名數據卷。
啟動容器並指定數據卷:

 docker run -d -p 8088:80 --name mynginx --mount type=volume,source=for_nginx,target=/usr/share/nginx/html nginx

可以查看下容器具體信息:

 docker inspect mynginx

通過頁面輸出的信息,在 Mounts 節點上,我們可以看到 Volume 在主機中的文件路徑。注意:Volume 掛載的文件必須是在 Docer 在主機上的指定位置的目錄。


        "Mounts": [
            {
                "Type": "volume",
                "Name": "for_nginx",
                "Source": "/var/lib/docker/volumes/for_nginx/_data",
                "Destination": "/usr/share/nginx/html",
                "Driver": "local",
                "Mode": "z",
                "RW": true,
                "Propagation": ""
            }
        ],

我們在宿主機數據卷里新增一個host.html

# 在主機上面運行
 cd /var/lib/docker/volumes/for_nginx/_data
 echo "This is host file." > host.html

進入容器內部查看是否也增加了host.html文件:

 docker exec -it mynginx /bin/bash
# 已進入Docker容器 mynginx 內部
 cd /usr/share/nginx/html && ls
# 可以查看到,在主機中創建的文件已在容器內部給列出來了。

瀏覽器 http://<這里換成宿主機IP地址>:8088/host.html 可以直接訪問到頁面,證明數據卷掛載成功。
如果強制刪除容器后,數據卷不會被刪除,還是會保存在宿主機docker/volumes路徑下。

 docker rm -f mynginx
# 查看文件
 cd /var/lib/docker/volumes/for_nginx/_data && ls

4、bind mounts(綁定數據卷)

bind mounts可以將宿主機任意目錄掛載到容器內
將宿主機/opt目錄掛載到容器內:

 docker run -d -p 8088:80 --name mynginx  --mount type=bind,source=/opt,target=/usr/share/nginx/html nginx

進入容器查看nginx默認html頁面:

 docker exec -it mynginx /bin/bash
# 已進入Docker容器 mynginx 內部
 cd /usr/share/nginx/html && ls
# 可以查看到,在主機中創建的文件已在容器內部給列出來了。

發現並沒有nginx默認的index.html和50.html頁面。

注:如果你使用Bind mounts掛載宿主機目錄到一個容器中的非空目錄,那么此容器中的非空目錄中的文件會被隱藏,容器訪問這個目錄時能夠訪問到的文件均來自於宿主機目錄。

那么如何掛載才不會覆蓋容器中的文件呢?

知識點:可以掛載具體的文件,不要掛載目錄。同時要掛載的文件必須要先存在,否則會掛載失敗。

#示例 
 docker run -d -p 8088:80 --name mynginx  -v/opt/a.html:/usr/share/nginx/html/a.html nginx

我們平時常用的掛載命令 -v 其實就是 --mount type=bind 的簡寫。

 docker run -d -p 8088:80 --name mynginx  --mount type=bind,source=/opt,target=/usr/share/nginx/html nginx
# 簡寫方式 (常用方式) 如下:
 docker run -d -p 8088:80 --name mynginx  -v/opt:/usr/share/nginx/html nginx

5、tmpfs mounts(臨時數據卷)

運行容器並綁定臨時卷:

docker run -d --name testnginx --mount type=tmpfs,target=/usr/share/nginx/html nginx

進入容器,創建文件並寫入測試數據:

docker exec -it testnginx /bin/bash
echo test > test.txt

刪除容器重新創建容器后發現數據丟失,可見臨時卷無法持久化數據。

6、三種存儲方式適用場景

volumes:

  • 多個運行容器間共享數據
  • 當Docker主機不確保具有給定的目錄或文件
  • 備份、恢復、或將數據從一個Docker主機遷移到另一個Docker主機時。

bind mount:

  • 主機與容器共享配置文件(Docker默認情況下通過這種方式為容器提供DNS解析,通過將/etc/resolv.conf掛載到容器中)

  • 共享源代碼或build artifacts(比如將Maven的target/目錄掛載到容器中,每次在Docker主機中build Maven工程時,容器能夠訪問到那些rebuilt artifacts)

  • 當 docker主機中的文件或目錄結構和容器需要的一致時。

tmpfs mount:

  • 既不想將數據存於主機,又不想存於容器中時(這可以是出於安全的考慮,或當應用需要寫大量非持久性的狀態數據時為了保護容器的性能而采取的方案)。

Docker 網絡

理解Docker0

#使用ip addr查看網卡信息
ip addr

當 Docker 啟動時,會自動在主機上創建一個 docker0 虛擬網橋,實際上是 Linux 的一個 bridge,可以理 解為一個軟件交換機。它會在掛載到它的網口之間進行轉發。

當創建一個 Docker 容器的時候,同時會創建了一對 veth pair 接口(當數據包發送到一個接口時,另外 一個接口也可以收到相同的數據包)。這對接口一端在容器內,即 eth0 ;另一端在本地並被掛載到docker0 網橋,名稱以 veth 開頭(例如 vethAQI2QT )。通過這種方式,主機可以跟容器通信,容器 之間也可以相互通信。Docker 就創建了在主機和所有容器之間一個虛擬共享網絡。

img

原理:

  1. 我們每啟動一個docker容器,docker就會給容器分配一個ip,我們只要安裝了docker,就會有一個網卡docker0。
  2. 橋接模式,使用的技術是veth-pair技術。
  3. 這個容器帶來的網卡都是一一對應的。
  4. veth-pair就是一對的虛擬設備接口,他們都是成對出現的,一端連着協議,一端彼此相連。
  5. 正因為有這個特性,veth-pair充當一個橋梁,連接各種虛擬網絡設備。

img

如上圖所示,tomcat01和tomcat02是公用的一個路由器,docker0 所有的容器不指定網絡的情況下,都是docker0路由的,docker會給我們的容器分配一個默認的可用IP。Docker 中的所有網絡接口都是虛擬的。虛擬的轉發效率高!只要刪除容器,對應的一對網橋就沒了。

容器訪問外網

容器要想訪問外部網絡,需要本地系統的轉發支持。在Linux 系統中,檢查轉發是否打開。

sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1

如果為 0,說明沒有開啟轉發,則需要手動打開。

sysctl -w net.ipv4.ip_forward=1

如果在啟動 Docker 服務的時候設定 --ip-forward=true, Docker 就會自動設定系統的 ip_forward 參數 為 1。

link 兩個可實現兩個容器之間互通

1、啟動兩個 busybox 容器實例:(注意要添加 -itd 參數)

docker run -itd --name t1 busybox && docker run -itd --name t2 busybox 

2、進行 ping 測試

docker exec -it t2 ping t1

img

如圖所示,上面的無法ping通!提示IP找不到。

重新啟動一個實例,使用 link 命令

#啟動 t3 容器
docker run -itd --name t3 --link t1 --link t2 busybox
#進行 ping 測試
docker exec -it t3 ping t1
docker exec -it t3 ping t2
#進入 t3 查看網絡映射配置
docker exec -it t3 cat /etc/hosts
#原理:link命令就是在運行的設備中配置了 hosts 網絡IP映射。

執行過程如下圖所示:

img

補充:以上配置,t3 是可以 ping 通 t1 和 t2 ,但 t1、t2 還是不能 ping 通 t3的。

總結:

  1. --link就是在hosts配置中增加一個映射。
  2. 此方法只能實現一個容器與另一個容器的互通,不能多個容器之間運行。
  3. 如果要實現多個容器的互通,需要采用其它方式。

查看網絡信息

docker network ls
docker network inspect 網絡ID

三種網絡模式:

# docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
97bfc37753c8   bridge    bridge    local
588bf06ad261   host      host      local
5efd1c6c22fd   none      null      local

# bridge:		橋接模式(默認)
# host:			和宿主機共享網絡
# none:			不配置網絡
# container:	容器網絡連通(用得少,局限大)

bridge 網絡應用示例

 docker run -itd --name b1 -P --net bridge busybox
 docker run -itd --name b2 -P --net bridge busybox
 docker run -itd --name b3 -P --net bridge busybox

# 以上可以一句代碼執行
docker run -itd --name b1 -P --net bridge busybox && docker run -itd --name b2 -P --net bridge busybox && docker run -itd --name b3 -P --net bridge busybox
# 創建一個busybox容器,IP自動分配,指定網絡名稱為 bridge,后台運行。
# 注意:這里使用busybox,使用的參數是 -itd ,為什么呢?

#測試
docker exec -it b1 ping b2  #不通
docker exec -it docker exec -it b3 ping 01a25b8ccc57 #不通
docker exec -it b1 ping 172.17.0.4 #通了
# docker0特點:默認,域名不能訪問,–-link可以打通

查看以上容器的配置信息

docker inspect b1

以上示例,使用默認的網絡,無法實現容器之間的互通,如何解決呢?自己建一個網絡。

創建 bridge 網絡

示例1:使用默認配置創建一個網絡

docker network create mynet #只是指定了網絡名稱為mynetwork,沒有其它配置

通過命令查看,發現默認就是 bridge 模式。

[root@localhost ~]# docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
02b391ca5efa   bridge    bridge    local
47a2cadc245f   host      host      local
dcc742ccb288   mynet     bridge    local
640e91b35c09   none      null      local
[root@localhost ~]#

查看網絡配置

[root@localhost ~]# docker network inspect mynet
[
    {
        "Name": "mynet",
        "Id": "dcc742ccb288333bb85cab8e0d2a0a2524afc3fed0a2a16a6af77dc021a72589",
        "Created": "2022-01-21T21:28:23.316025804+08:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {},
        "Options": {},
        "Labels": {}
    }
]
[root@localhost ~]#

示例2——指定子網和網關

docker network create --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynetwork
# docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynetwork
#解釋:docker network create 橋接的網絡模式(這個參數一般省略) 子網 網關 名稱

查看網絡配置

[root@localhost ~]# docker network inspect mynetwork
[
    {
        "Name": "mynetwork",
        "Id": "7aa4be7ee31f6821ae5d3a60a17c943cfba2df9ce53da77c867a6168cc719a58",
        "Created": "2022-01-21T21:32:13.508461709+08:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "192.168.0.0/16",
                    "Gateway": "192.168.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {},
        "Options": {},
        "Labels": {}
    }
]
[root@localhost ~]#

同一網絡下的容器直接互相ping通

#清空所有容器
docker rm -f $(docker ps -aq)

#啟動3個容器,使用 mynet 網絡
docker run -itd --name a1 -P --net mynet busybox && docker run -itd --name a2 -P --net mynet busybox && docker run -itd --name a3 -P --net mynet busybox

#查看容器列表
docker ps

#查看網絡配置
docker network inspect mynet

img

#測試網絡 以下測試全部通過
docker exec -it a1 ping a2
docker exec -it a1 ping a3
docker exec -it a2 ping a1
docker exec -it a2 ping a3
docker exec -it a3 ping a1
docker exec -it a3 ping a2

運行結果就不放圖了,通過以上測試,得到一個結論:

在同一網絡下(不是默認的 bridge),容器實例之間是互通的。

網絡互通

繼續上一個測試:

#啟動3個容器,使用 mynetwork 網絡
docker run -itd --name b1 -P --net mynetwork busybox && docker run -itd --name b2 -P --net mynetwork busybox && docker run -itd --name b3 -P --net mynetwork busybox

#查看容器列表
docker ps

#查看網絡配置
docker network inspect mynetwork

#測試網絡
docker exec -it b1 ping b2	#通過
docker exec -it b1 ping a1	#不通

img

其實,不同網絡下的容器也能互相連通,執行下面這句代碼:

#網絡互通
docker network connect mynet b1  #把 b1 (在mynetwork網絡下)加入到 mynet 網絡下
docker network connect mynet b2  #把 b2 (在mynetwork網絡下)加入到 mynet 網絡下
docker network connect mynet b3  #把 b3 (在mynetwork網絡下)加入到 mynet 網絡下

#然后再試
docker exec -it a1 ping b1 #通過
docker exec -it a1 ping b2 #通過
docker exec -it a1 ping b3 #通過

#再次查看 mynet 網絡,b1、b2、b3都加入了 mynet 網絡,同時又重新分配了各自的獨立IP地址。
docker network inspect mynet

#結論 網絡互聯就是在網絡配置中添加了網絡映射

img

Docker底層實現和網絡實現

Docker 底層的核心技術包括 Linux 上的名字空間(Namespaces)、控制組(Control groups)、Union 文件系統(Union file systems)和容器格式(Container format)。

Docker 的網絡實現其實就是利用了 Linux 上的網絡名字空間和虛擬網絡設備(特別是 veth pair)。

創建容器時指定IP

Docker創建容器時默認采用bridge網絡,自行分配ip,不允許自己指定。

在實際部署中,我們需要指定容器ip,不允許其自行分配ip,尤其是搭建集群時,固定ip是必須的。

我們可以創建自己的bridge網絡 : mynet,創建容器的時候指定網絡為mynet並指定ip即可。

創建容器並指定容器IP

docker run -it --name nginx-second --network=dockercompose --ip 172.19.0.6 nginx
# 驗證是否固定
docker insepect 容器ID | grep "IpAddress"
"SecondaryIPAddresses": null, "IPAddress": "", "IPAddress": "172.19.0.6"

DockerFile 構建鏡像

此部分內容參照 https://www.cnblogs.com/qsing/p/15160974.html 進行演練。

通過一張圖來了解 Docker 鏡像、容器和 Dockerfile 三者之間的關系。

img

Dockerfile 概念

Docker 鏡像是一個特殊的文件系統,除了提供容器運行時所需的程序、庫、資源、配置等文件外,還包含了一些為運行時准備的一些配置參數(如匿名卷、環境變量、用戶等)。鏡像不包含任何動態數據,其內容在構建之后也不會被改變。

鏡像的定制實際上就是定制每一層所添加的配置、文件。如果我們可以把每一層修改、安裝、構建、操作的命令都寫入一個腳本,用這個腳本來構建、定制鏡像,那么之前提及的無法重復的問題、鏡像構建透明性的問題、體積的問題就都會解決。這個腳本就是 Dockerfile。

Dockerfile 是一個文本文件,其內包含了一條條的指令(Instruction),每一條指令構建一層,因此每一條指令的內容,就是描述該層應當如何構建。有了 Dockerfile,當我們需要定制自己額外的需求時,只需在 Dockerfile 上添加或者修改指令,重新生成 image 即可,省去了敲命令的麻煩。

查看一個鏡像的處理過程(Show the history of an image):

[root@localhost ~]# docker history nginx
IMAGE          CREATED       CREATED BY                                      SIZE      COMMENT
605c77e624dd   4 days ago    /bin/sh -c #(nop)  CMD ["nginx" "-g" "daemon…   0B
<missing>      4 days ago    /bin/sh -c #(nop)  STOPSIGNAL SIGQUIT           0B
<missing>      4 days ago    /bin/sh -c #(nop)  EXPOSE 80                    0B
<missing>      4 days ago    /bin/sh -c #(nop)  ENTRYPOINT ["/docker-entr…   0B
<missing>      4 days ago    /bin/sh -c #(nop) COPY file:09a214a3e07c919a…   4.61kB
<missing>      4 days ago    /bin/sh -c #(nop) COPY file:0fd5fca330dcd6a7…   1.04kB
<missing>      4 days ago    /bin/sh -c #(nop) COPY file:0b866ff3fc1ef5b0…   1.96kB
<missing>      4 days ago    /bin/sh -c #(nop) COPY file:65504f71f5855ca0…   1.2kB
<missing>      4 days ago    /bin/sh -c set -x     && addgroup --system -…   61.1MB
<missing>      4 days ago    /bin/sh -c #(nop)  ENV PKG_RELEASE=1~bullseye   0B
<missing>      4 days ago    /bin/sh -c #(nop)  ENV NJS_VERSION=0.7.1        0B
<missing>      4 days ago    /bin/sh -c #(nop)  ENV NGINX_VERSION=1.21.5     0B
<missing>      13 days ago   /bin/sh -c #(nop)  LABEL maintainer=NGINX Do…   0B
<missing>      13 days ago   /bin/sh -c #(nop)  CMD ["bash"]                 0B
<missing>      13 days ago   /bin/sh -c #(nop) ADD file:09675d11695f65c55…   80.4MB
[root@localhost ~]#

DockerFile文件格式

##  Dockerfile文件格式

# This dockerfile uses the ubuntu image
# VERSION 2 - EDITION 1
# Author: docker_user
# Command format: Instruction [arguments / command] ..
 
# 1、第一行必須指定基礎鏡像信息
FROM ubuntu
 
# 2、維護者信息
MAINTAINER docker_user docker_user@email.com
 
# 3、鏡像操作指令
RUN echo "deb http://archive.ubuntu.com/ubuntu/ raring main universe" >> /etc/apt/sources.list
RUN apt-get update && apt-get install -y nginx
RUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf
 
# 4、容器啟動執行指令
CMD /usr/sbin/nginx

Dockerfile 分為四部分:基礎鏡像信息、維護者信息、鏡像操作指令、容器啟動執行指令。一開始必須要指明所基於的鏡像名稱,接下來一般會說明維護者信息;后面則是鏡像操作指令,例如 RUN 指令。每執行一條RUN 指令,鏡像添加新的一層,並提交;最后是 CMD 指令,來指明運行容器時的操作命令。

1、注釋

一個標准的dockerfile,注釋是必須的。

#這是dockerfile注釋,dockerfile中指令以"CMD args"格式出現
CMD args
CMD args
...

一個Dockerfile 第一個指令必須是FROM指令,用於指定基礎鏡像,那么基礎鏡像的父鏡像從哪里來?答案是scratch帶有該FROM scratch指令的 Dockerfile會創建一個基本映像

2.解析器指令

解析器指令是可選的,會影響 aDockerfile中后續行的處理方式。解析器指令不會向構建添加層,也不會顯示為構建步驟,單個指令只能使用一次。

dockerfile目前支持以下兩個解析器指令:

  • syntax
  • escape
2.1syntax

此功能僅在使用BuildKit后端時可用,在使用經典構建器后端時會被忽略。

我們可以在dockerfile文件開頭指定此dockerfile語法解析器,如下:

# syntax=docker/dockerfile:1
# syntax=docker.io/docker/dockerfile:1
# syntax=example.com/user/repo:tag@sha256:abcdef...

通過syntax自定義 Dockerfile 語法解析器可以實現如下:

  • 在不更新 Docker 守護進程的情況下自動修復錯誤
  • 確保所有用戶都使用相同的解析器來構建您的 Dockerfile
  • 無需更新 Docker 守護程序即可使用最新功能
  • 在將新功能或第三方功能集成到 Docker 守護進程之前試用它們
  • 使用替代的構建定義,或創建自己的定義

官方dockerfile解析器:

  • docker/dockerfile:1 不斷更新最新的1.x.x次要補丁版本
  • docker/dockerfile:1.2 保持更新最新的1.2.x補丁版本,一旦版本1.3.0發布就停止接收更新。
  • docker/dockerfile:1.2.1 不可變:從不更新1.2版本

比如我們使用1.2最新補丁版本,我們的Dockerfile如下:

#syntax=docker/dockerfile:1.2
FROM busybox
run echo 123

我們啟用buildkit構建

# DOCKER_BUILDKIT=1 docker build -t busybox:v1 .
[+] Building 5.8s (8/8) FINISHED                                                                           
 => [internal] load build definition from Dockerfile                                                  0.3s
 => => transferring dockerfile: 150B                                                                  0.0s
 => [internal] load .dockerignore                                                                     0.4s
 => => transferring context: 2B                                                                       0.0s
 => resolve image config for docker.io/docker/dockerfile:1.2                                          2.6s
 => CACHED docker-image://docker.io/docker/dockerfile:1.2@sha256:e2a8561e419ab1ba6b2fe6cbdf49fd92b95  0.0s
 => [internal] load metadata for docker.io/library/busybox:latest                                     0.0s
 => [1/2] FROM docker.io/library/busybox                                                              0.3s
 => [2/2] RUN echo 123                                                                                1.1s
 => exporting to image                                                                                0.3s
 => => exporting layers                                                                               0.3s
 => => writing image sha256:bd66a3db9598d942b68450a7ac08117830b4d66b68180b6e9d63599d01bc8a04          0.0s
 => => naming to docker.io/library/busybox:v1
2.2 escape

通過escape定義dockerfile的換行拼接轉義符

# escape=\   

如果要構建一個window鏡像就有大用處了,我們看下面dockerfile

FROM microsoft/nanoserver
COPY testfile.txt c:\\
RUN dir c:\

由於默認轉義符為\,則在構建的第二步step2會是這樣COPY testfile.txt c:\RUN dir c:顯然與我們的預期不符。

我們把轉義符換成`號即可

# escape=`

FROM microsoft/nanoserver
COPY testfile.txt c:\ `
RUN dir c:\

3.類bash的環境變量

FROM busybox
ENV FOO=/bar
WORKDIR ${FOO}   # WORKDIR /bar
ADD . $FOO       # ADD . /bar
COPY \$FOO /quux # COPY $FOO /quux

${variable_name}語法還支持bash 指定的一些標准修飾符:

  • ${variable:-word}表示如果variable變量被設置(存在),則結果將是該值。如果variable未設置,word則將是結果。
  • ${variable:+word}表示如果variable被設置則為word結果,否則為空字符串。

4. .dockerignore

.dockerignore用於忽略CLI發送到docker守護進程的文件或目錄。以下是一個.dockerignore文件

#.dockeringre可以有注釋
*.md
!README.md
temp?
*/temp*
*/*/temp*
規則 行為
*/temp* 排除名稱以temp根目錄的任何直接子目錄開頭的文件和目錄。例如,純文件/somedir/temporary.txt被排除在外,目錄/somedir/temp.
*/*/temp* 排除temp從根目錄下兩級的任何子目錄開始的文件和目錄。例如,/somedir/subdir/temporary.txt被排除在外。
temp? 排除根目錄中名稱為一個字符擴展名的文件和目錄temp。例如,/tempa/tempb被排除在外。
不排除到文件

DockerFile 創建鏡像

1、docker build

以 nginx 鏡像為例:

在一個空白目錄中,建立一個文本文件,並命名為 Dockerfile

mkdir mynginx && cd mynginx && touch Dockerfile && vim Dockerfile

其內容為:

FROM nginx
RUN echo '<h1>Hello,Docker!</h1>' > /usr/share/nginx/html/index.html

這個 Dockerfile 文件很簡單,一共就兩行。涉及了兩條指令,FROM 和 RUN 。

運行命令:

docker build -t nginx:v1 .

通過 build 指定了目標鏡像的標簽為 nginx:v1 ,以及 Dockerfile 的上下文 context。

什么是 docker 上下文?

一個面向服務端的目錄夾結構,除了 Dockerfile ,你的一切構建資源都應該在這個目標(指定的上下文)中。

上面命令最后面的 . 表示當前目錄中的所有文件。

上下文是遞歸處理的。因此,如果是 PATH 則包含任何子目錄,如果是 URL 則包含存儲庫及其子模塊。

關鍵點,構建是由 Docker 守護程序運行,而不是由 CLI 運行,所以 docker 會把上下文資源打包傳輸給守護進程進行構建,為了減少不必要的臃腫,最好是從一個空目錄作為上下文開始,並將 Dockerfile 保存在該目錄中。

強調:僅添加構建 Dockerfile 所需的文件。

我們可以使用 -f選項指定 dockerfile

docker build -f ../Dockerfile -t nginx:v1 .

使用多個 -t選項保持多個 tag。

docker build -t nginx:v1 -t nginx:v2 .

結果如下所示:

[root@localhost mynginx]# docker build -t nginx:v1 -t nginx:v2 .
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM nginx
 ---> 605c77e624dd
Step 2/2 : RUN echo '<h1>Hello,Docker!</h1>' > /usr/share/nginx/html/index.html
 ---> Running in 500c662be6bb
Removing intermediate container 500c662be6bb
 ---> 8c99d51744af
Successfully built 8c99d51744af
Successfully tagged nginx:v1
Successfully tagged nginx:v2
[root@localhost mynginx]#
[root@localhost mynginx]# docker images
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
nginx        v1        8c99d51744af   46 seconds ago   141MB
nginx        v2        8c99d51744af   46 seconds ago   141MB

【結果】構建了兩個不同 tag 的同一鏡像。

PS:如果多次執行相同的構建命令,查看 像像列表,會發現沒有新的鏡像生成,原因是使用了緩存。添加 --no-cache 命令可以解決。

[root@localhost mynginx]# docker build --no-cache -t nginx:v1 -t nginx:v2 .
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM nginx
 ---> 605c77e624dd
Step 2/2 : RUN echo '<h1>Hello,Docker!</h1>' > /usr/share/nginx/html/index.html
 ---> Running in 63ac8e9cf933
Removing intermediate container 63ac8e9cf933
 ---> 413890de8389
Successfully built 413890de8389
Successfully tagged nginx:v1
Successfully tagged nginx:v2
[root@localhost mynginx]# docker images
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
nginx        v1        413890de8389   5 seconds ago    141MB
nginx        v2        413890de8389   5 seconds ago    141MB
<none>       <none>    8c99d51744af   18 minutes ago   141MB
busybox      latest    beae173ccac6   3 weeks ago      1.24MB
nginx        latest    605c77e624dd   3 weeks ago      141MB
tomcat       latest    fb5657adc892   4 weeks ago      680MB
[root@localhost mynginx]#

2、BuildKit

buildkit 將 Dockerfile 變成了 Docker 鏡像。它不只是構建 Docker 鏡像,它可以構建 OCI 圖像和其他幾種輸出格式。

從版本18.09開始,Docker 支持由 moby / buildkit 項目提供的用於執行構建 的新后端。與舊的實現相比,BuildKit 后端提供了許多好處。例如,BuildKit 可以:

  • 檢測並路過執行未使用的構建階段。
  • 平行構建獨立的構建階段。
  • 在不同的構建過程,只增加傳輸構建上下文中的更自以為文件。
  • 在構建上下文中檢測並路過傳輸未使用的文件。
  • 使用外部 Dockerfile 實現許多新功能。
  • 避免與 API 的其它部分(中間鏡像和容器)產生副作用。
  • 優先處理您的構建緩存,以便自動修剪。

要使用 BuildKit 后端,只需要在調用 DOCKER_BUILDKIT=1 docker build 之前在 CLI 上設置環境變量 DOCKER_BUILDKIT=1。或者配置 /etc/docker/daemon.json 啟用。

環境變量

export  DOCKER_BUILDKIT=1
export  COMPOSE_DOCKER_CLI_BUILD=1

注入到 .bashrc 文件

echo -e  "export DOCKER_BUILDKIT=1" >> ~/.bashrc
echo -e  "export COMPOSE_DOCKER_CLI_BUILD=1" >> ~/.bashrc

修改/etc/docker/daemon.json

	"features": {
		"buildkit": true
	}

重啟docker-daemon和docker

sudo systemctl daemon-reload && sudo systemctl restart docker

演示如下:

 DOCKER_BUILDKIT=1 docker build --no-cache  -t nginx:v3 .

img

DockerFile的指令

1、FROM 指定基礎鏡像

FROM 指令用於指定其后構建新鏡像所使用的基礎鏡像。FROM 指令必是 Dockerfile 文件中的首條命令,啟動構建流程后,Docker 將會基於該鏡像構建新鏡像,FROM 后的命令也會基於這個基礎鏡像。

FROM語法格式為:

FROM <image>
FROM <image>:<tag>
FROM <image>:<digest>

通過 FROM 指定的鏡像,可以是任何有效的基礎鏡像。FROM 有以下限制:

  • FROM 必須 是 Dockerfile 中第一條非注釋命令
  • 在一個 Dockerfile 文件中創建多個鏡像時,FROM 可以多次出現。只需在每個新命令 FROM 之前,記錄提交上次的鏡像 ID。
  • tag 或 digest 是可選的,如果不使用這兩個值時,會使用 latest 版本的基礎鏡像

2、RUN 執行命令

在鏡像的構建過程中執行特定的命令,並生成一個中間鏡像。

#shell格式
RUN <command>
#exec格式
RUN ["executable", "param1", "param2"]
  • RUN 命令將在當前 image 中執行任意合法命令並提交執行結果。命令執行提交后,就會自動執行 Dockerfile 中的下一個指令。
  • 層級 RUN 指令和生成提交是符合 Docker 核心理念的做法。它允許像版本控制那樣,在任意一個點,對 image 鏡像進行定制化構建。
  • RUN 指令創建的中間鏡像會被緩存,並會在下次構建中使用。如果不想使用這些緩存鏡像,可以在構建時指定 --no-cache 參數,如:docker build --no-cache

3、COPY 復制文件

COPY <源路徑>... <目標路徑>
COPY ["<源路徑1>",... "<目標路徑>"]

和 RUN 指令一樣,也有兩種格式,一種類似於命令行,一種類似於函數調用。COPY 指令將從構建上下文目錄中 <源路徑> 的文件/目錄復制到新的一層的鏡像內的<目標路徑>位置。比如:

COPY package.json /usr/src/app/

<源路徑>可以是多個,甚至可以是通配符,其通配符規則要滿足 Go 的 filepath.Match 規則,如:

COPY hom* /mydir/
COPY hom?.txt /mydir/

<目標路徑>可以是容器內的絕對路徑,也可以是相對於工作目錄的相對路徑(工作目錄可以用 WORKDIR 指令來指定)。目標路徑不需要事先創建,如果目錄不存在會在復制文件前先行創建缺失目錄。

此外,還需要注意一點,使用 COPY 指令,源文件的各種元數據都會保留。比如讀、寫、執行權限、文件變更時間等。這個特性對於鏡像定制很有用。特別是構建相關文件都在使用 Git 進行管理的時候。

4、ADD 更高級的復制文件

ADD 指令和 COPY 的格式和性質基本一致。

在 Docker 官方的 Dockerfile 最佳實踐文檔 中要求,盡可能的使用 COPY,因為 COPY 的語義很明確,就是復制文件而已,而 ADD 則包含了更復雜的功能,其行為也不一定很清晰。最適合使用 ADD 的場合,就是所提及的需要自動解壓縮的場合。

另外需要注意的是,ADD 指令會令鏡像構建緩存失效,從而可能會令鏡像構建變得比較緩慢。

因此在 COPYADD 指令中選擇的時候,可以遵循這樣的原則,所有的文件復制均使用 COPY 指令,僅在需要自動解壓縮的場合使用 ADD

在構建鏡像時,復制上下文中的文件到鏡像內,格式:

ADD <源路徑>... <目標路徑>
ADD ["<源路徑>",... "<目標路徑>"]

5、ENV 設置環境變量

格式有兩種:

ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...

這個指令很簡單,就是設置環境變量而已,無論是后面的其它指令,如 RUN,還是運行時的應用,都可以直接使用這里定義的環境變量。

 ENV VERSION=1.0 DEBUG=on \
    NAME="Happy Feet"

這個例子中演示了如何換行(在 Linux 中,\ 表示換行),以及對含有空格的值用雙引號括起來的辦法,這和 Shell 下的行為是一致的。

6、EXPOSE

為構建的鏡像設置監聽端口,使容器在運行時監聽。格式:

EXPOSE <port> [<port>...]

EXPOSE 指令並不會讓容器監聽 host 的端口,如果需要,需要在 docker run 時使用 -p-P 參數來發布容器端口到 host 的某個端口上。

7、VOLUME 定義匿名卷

VOLUME用於創建掛載點,即向基於所構建鏡像創始的容器添加卷:

VOLUME ["/data"]

一個卷可以存在於一個或多個容器的指定目錄,該目錄可以繞過聯合文件系統,並具有以下功能:

  • 卷可以容器間共享和重用
  • 容器並不一定要和其它容器共享卷
  • 修改卷后會立即生效
  • 對卷的修改不會對鏡像產生影響
  • 卷會一直存在,直到沒有任何容器在使用它

VOLUME 讓我們可以將源代碼、數據或其它內容添加到鏡像中,而又不並提交到鏡像中,並使我們可以多個容器間共享這些內容。

8、CMD容器啟動命令

CMD用於指定在容器啟動時所要執行的命令。CMD 有以下三種格式:

CMD ["executable","param1","param2"]
CMD ["param1","param2"]
CMD command param1 param2

省略可執行文件的 exec 格式,這種寫法使 CMD 中的參數當做 ENTRYPOINT 的默認參數,此時 ENTRYPOINT 也應該是 exec 格式,具體與 ENTRYPOINT 的組合使用,參考 ENTRYPOINT。

注意 與 RUN 指令的區別:RUN 在構建的時候執行,並生成一個新的鏡像,CMD 在容器運行的時候執行,在構建時不進行任何操作。

9、ENTRYPOINT入口點

ENTRYPOINT 指定這個容器啟動的時候要運行的命令,可以追加命令。

ENTRYPOINT 用於給容器配置一個可執行程序。也就是說,每次使用鏡像創建容器時,通過 ENTRYPOINT 指定的程序都會被設置為默認程序。ENTRYPOINT 有以下兩種形式:

ENTRYPOINT ["executable", "param1", "param2"]
ENTRYPOINT command param1 param2

ENTRYPOINT 與 CMD 非常類似,不同的是通過docker run執行的命令不會覆蓋 ENTRYPOINT,而docker run命令中指定的任何參數,都會被當做參數再次傳遞給 ENTRYPOINT。Dockerfile 中只允許有一個 ENTRYPOINT 命令,多指定時會覆蓋前面的設置,而只執行最后的 ENTRYPOINT 指令。

docker run運行容器時指定的參數都會被傳遞給 ENTRYPOINT ,且會覆蓋 CMD 命令指定的參數。如,執行docker run <image> -d時,-d 參數將被傳遞給入口點。

也可以通過docker run --entrypoint重寫 ENTRYPOINT 入口點。如:可以像下面這樣指定一個容器執行程序:

ENTRYPOINT ["/usr/bin/nginx"]

10、USER 指定當前用戶

USER 用於指定運行鏡像所使用的用戶:

USER daemon

使用USER指定用戶時,可以使用用戶名、UID 或 GID,或是兩者的組合。以下都是合法的指定試:

USER user
USER user:group
USER uid
USER uid:gid
USER user:gid
USER uid:group

使用USER指定用戶后,Dockerfile 中其后的命令 RUN、CMD、ENTRYPOINT 都將使用該用戶。鏡像構建完成后,通過 docker run 運行容器時,可以通過 -u 參數來覆蓋所指定的用戶。

11、WORKDIR 指定工作目錄

WORKDIR用於在容器內設置一個工作目錄:

WORKDIR /path/to/workdir

通過WORKDIR設置工作目錄后,Dockerfile 中其后的命令 RUN、CMD、ENTRYPOINT、ADD、COPY 等命令都會在該目錄下執行。 如,使用WORKDIR設置工作目錄:

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

在以上示例中,pwd 最終將會在 /a/b/c 目錄中執行。在使用 docker run 運行容器時,可以通過-w參數覆蓋構建時所設置的工作目錄。

12、LABEL為鏡像添加元數據

LABEL用於為鏡像添加元數據,元數以鍵值對的形式指定:

LABEL <key>=<value> <key>=<value> <key>=<value> ...

使用LABEL指定元數據時,一條LABEL指定可以指定一或多條元數據,指定多條元數據時不同元數據之間通過空格分隔。推薦將所有的元數據通過一條LABEL指令指定,以免生成過多的中間鏡像。 如,通過LABEL指定一些元數據:

LABEL version="1.0" description="這是一個Web服務器" by="Docker筆錄"

指定后可以通過docker inspect查看:

docker inspect itbilu/test
"Labels": {
    "version": "1.0",
    "description": "這是一個Web服務器",
    "by": "Docker筆錄"
},

13、ARG構建參數

ARG用於指定傳遞給構建運行時的變量:

ARG <name>[=<default value>]

如,通過ARG指定兩個變量:

ARG site
ARG build_user=IT筆錄

以上我們指定了 site 和 build_user 兩個變量,其中 build_user 指定了默認值。在使用 docker build 構建鏡像時,可以通過 --build-arg <varname>=<value> 參數來指定或重設置這些變量的值。

docker build --build-arg site=itiblu.com -t itbilu/test .

這樣我們構建了 itbilu/test 鏡像,其中site會被設置為 itbilu.com,由於沒有指定 build_user,其值將是默認值 IT 筆錄。

14、ONBUILD

ONBUILD用於設置鏡像觸發器:

ONBUILD [INSTRUCTION]

當所構建的鏡像被用做其它鏡像的基礎鏡像,該鏡像中的觸發器將會被觸發。 如,當鏡像被使用時,可能需要做一些處理:

[...]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
[...]

15、STOPSIGNAL

STOPSIGNAL用於設置停止容器所要發送的系統調用信號:

STOPSIGNAL signal

所使用的信號必須是內核系統調用表中的合法的值,如:SIGKILL。

16、SHELL指令

SHELL用於設置執行命令(shell式)所使用的的默認 shell 類型:

SHELL ["executable", "parameters"]

SHELL 在 Windows 環境下比較有用,Windows 下通常會有 cmdpowershell 兩種 shell,可能還會有 sh。這時就可以通過 SHELL 來指定所使用的 shell 類型:

FROM microsoft/windowsservercore

# Executed as cmd /S /C echo default
RUN echo default

# Executed as cmd /S /C powershell -command Write-Host default
RUN powershell -command Write-Host default

# Executed as powershell -command Write-Host hello
SHELL ["powershell", "-command"]
RUN Write-Host hello

# Executed as cmd /S /C echo hello
SHELL ["cmd", "/S"", "/C"]
RUN echo hello

DockerFile實例

[root@localhost ~]# cd /opt/
[root@localhost opt]# mkdir nginx   ##創建Nginx目錄
[root@localhost opt]# cd nginx/
[root@localhost nginx]# vim Dockerfile

FROM centos:7
MAINTAINER The is nginx <Gerry>
RUN yum install -y proc-devel gcc gcc-c++ zlib zlib-devel make openssl-devel wget && wget http://nginx.org/download/nginx-1.21.1.tar.gz
ADD nginx-1.21.1.tar.gz /usr/local
WORKDIR /usr/local/nginx-1.21.1/
RUN ./configure --prefix=/usr/local/nginx && make && make install
EXPOSE 80
EXPOSE 443
RUN echo "daemon off;">>/usr/local/nginx/conf/nginx.conf
WORKDIR /root/nginx
ADD run.sh /run.sh
RUN chmod 755 /run.sh
CMD ["/run.sh"]
[root@localhost nginx]# vim run.sh

#!/bin/bash
/usr/local/nginx/sbin/nginx   ##開啟Nginx服務

[root@localhost nginx]# mount.cifs //192.168.100.3/LNMP-C7 /mnt/  ##掛載鏡像
Password for root@//192.168.100.3/LNMP-C7:  
[root@localhost nginx]# cp /mnt/nginx-1.12.2.tar.gz ./   ##復制到當前目錄下
[root@localhost nginx]# docker build -t nginx:new .   ##創建鏡像
[root@localhost nginx]# docker run -d -P nginx:new    ##創建容器
228c1f5b8070d52c6f19d03159ad93a60d682a586c0b1f944dc651ee40576a3e
[root@localhost nginx]# docker ps -a   ##查看容器
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                        PORTS                                           NAMES
228c1f5b8070        nginx:new           "/run.sh"                9 seconds ago       Up 8 seconds                  0.0.0.0:32770->80/tcp, 0.0.0.0:32769->443/tcp   busy_booth

Docker 倉庫

(一)官方標配:Registry 私有鏡像倉庫

  Docker Hub作為Docker默認官方公共鏡像,如果想要自己搭建私有鏡像殘酷,官方也提供Registry鏡像,使得我們搭建私有倉庫變得非常簡單。

  所謂私有倉庫,也就是在本地(局域網)搭建的一個類似公共倉庫的東西,搭建好之后,我們可以將鏡像提交到私有倉庫中。這樣我們既能使用 Docker 來運行我們的項目鏡像,也避免了商業項目暴露出去的風險。

  下面就是詳細的基於Registry搭建私有倉庫的步驟,首先我們可以准備兩台服務器,這里我有兩台Linux服務主機,他們的角色如下:

主機名 角色 備注
192.168.3.250 registry-server 部署registry容器
192.168.3.48 registry-consumer 從registry服務器上下載鏡像使用

1.1 搭建鏡像倉庫

首先,下載Registry鏡像並啟動

docker pull registry

然后,運行一個Registry鏡像倉庫的容器實例

docker run -d -v /opt/images/registry:/var/lib/registry \
-p 5000:5000 \
--restart=always \
--name gerry-registry registry \

最后,在客戶端查看鏡像倉庫中的所有鏡像

curl http://your-server-ip:5000/v2/_catalog
# curl http://localhost:5000/v2/_catalog

1.2 上傳鏡像

  首先,為了讓客戶端服務器能夠快速地訪問剛剛在服務端搭建的鏡像倉庫(默認情況下是需要配置HTTPS證書的),這里簡單在客戶端配置一下私有倉庫的可信任設置讓我們可以通過HTTP直接訪問:# vim /etc/docker/daemon.json

  加上下面這一句,這里的“your-server-ip”請換為你的服務器的外網IP地址:

{ 
    "insecure-registries" : [ "your-server-ip:5000" ] 
}

PS:*如果不設置可信任源,又沒有配置HTTPS證書,那么會遇到這個錯誤:error: Get https://ip:port/v1/_ping: http: server gave HTTP response to HTTPS client.

​ 為了使得配置生效,重新啟動docker服務:

# systemctl restart docker

  其次,為要上傳的鏡像打Tag

docker tag your-image-name:tagname your-server-ip:5000/your-image-name:tagname

​ 最后,開始正式上傳鏡像到服務端鏡像倉庫

docker push your-registry-server-ip:5000/your-image-name:tagname

​ 這時我們可以再次通過訪問API驗證鏡像倉庫的內容:

curl http://your-server-ip:5000/v2/_catalog

1.3 下載鏡像

​ 下載鏡像就很簡單了,使用pull命令即可:

docker pull your-server-ip:5000/your-image-name:tagname

​ 如果想要知道要下載的鏡像都有哪些tag(或版本),可以通過下面這個api來獲取:

curl http://your-server-ip:5000/v2/your-image-name/tags/list

(二)共享源頭:Docker Hub 公共鏡像倉庫

​ 程序員都喜歡用Git,如果把Registry私有倉庫比作GitLab的話,那么Docker Hub公共倉庫就類似於GitHub,這是一個公共的共享的鏡像倉庫平台,我們可以像在GitHub上隨意得clone公共的開源項目一樣pull鏡像到本地。下面就是基於Docker Hub建立公共倉庫的步驟:

2.1 注冊和創建倉庫

​ 首先,你得去docker hub上注冊一個賬號,如已有賬號,直接登錄。

​ 其次,注冊完成登錄之后就可以創建一個Repository。注意:創建Publish類型的,如果創建Private類型的,是需要證書的。

2.2 客戶端操作

​ 創建完倉庫,我們就可以在客戶端上登錄:

方式一:docker login
方式二:docker login --username=your-account

​ 登錄之后,就可以為鏡像打Tag:

docker tag xdp-service-runtime:2.2 edisonsaonian/xdp-service-runtime:2.2

​ 打完Tag就可以推送到遠程倉庫啦:

docker push edisonsaonian/xdp-service-runtime:2.2

​ 這時,便可以到docker hub上查看Repository的信息。當然,我們可以在另外的客戶端上拉取這個剛剛上傳的鏡像了。

​ 怎么樣,是不是很Easy,Enjoy

(三)企業最愛:Harbor 企業級鏡像倉庫

​ Harbor是VMware公司開源的一個企業級Docker Registry項目,項目地址:https://github.com/goharbor/harbor

​ Harbor作為一個企業級私有Registry服務器,提供了更好的性能和安全,提升了用戶使用Registry構建和運行環境傳輸鏡像的效率。雖然Harbor和Registry都是私有鏡像倉庫的選擇,但是Harbor的企業級特性更強,因此也是更多企業級用戶的選擇。

  Harbor實現了基於角色的訪問控制機制,並通過項目來對鏡像進行組織和訪問權限的控制,也常常和K8S中的namespace結合使用。此外,Harbor還提供了圖形化的管理界面,我們可以通過瀏覽器來瀏覽,檢索當前Docker鏡像倉庫,管理項目和命名空間。

  有關Harbor的架構,可以參考閱讀這一篇《Harbor整體架構》一文,里面講述了Harbor的6大核心組件構成,有興趣的朋友可以一讀。

  下面列出了Harbor的搭建過程,主要參考自Harbor的github文檔:

3.1 一些准備工作

​ (1)下載離線安裝包

  Harbor提供了兩種安裝方式:一種是在線安裝包,因此包很小;另一種是離線安裝包,因此包很大(>=570MB)。這里選擇下載離線安裝包,下載地址:https://github.com/goharbor/harbor/releases

  這里選擇版本為v2.4.1,下載完成后傳輸到你的服務器上並解壓:

tar zvxf harbor-offline-installer-v2.4.1.tgz

  (2)安裝docker

  如果還沒有安裝docker,那么請先安裝docker,已安裝則跳過。

# yum install docker
# systemctl start docker.service

  (3)安裝docker-compose

  這里選擇Github源:下載並設置權限

sudo sudocurl -L https://get.daocloud.io/docker/compose/releases/download/1.29.2/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

 驗證:

docker-compose -version

PS:如果想要卸載docker-compose,請執行以下命令

sudo rm /usr/local/bin/docker-compose

3.2 自簽TLS證書

  雖然對於所有要求配置HTTPS的要求我都是比較抵觸的,不過考慮到去年公司官網被攻擊並且還被Google列入黑名單導致公司官網好幾天不可用,我們信息中心遭受了很大的壓力,因此還是老老實實地為所有有需要的地方配上HTTPS吧。當然,這里演示的只是我們自己創建的證書,實際生產環境中我們切記還是需要去阿里雲或者其他雲服務器廠商申請免費或收費的證書。

  此小節內容主要參考自Harbor的HTTPS配置文檔

  (1)創建存放證書的目錄

mkdir -p /data/cert/
cd   /data/cert/

  (2)創建自簽名證書key文件並生成證書請求:

  注意:這里reg.edisonedu.com需要替換為你的域名,我這里是隨便取的,會在后面更改DNS。

openssl genrsa -out reg.edisonedu.com.key 4096
openssl req -x509 -new -nodes -sha512 -days 365 \
    -subj "/C=TW/ST=Taipei/L=Taipei/O=example/OU=Personal/CN=reg.edisonedu.com" \
    -key reg.edisonedu.com.key \
    -out reg.edisonedu.com.crt

3.3 Harbor安裝與配置

  在解壓的harbor目錄中編輯harbor.cfg文件,修改以下內容:

#配置文件改名(從模板文件復制)
cp harbor.yml.tmpl harbor.yml
#然后進行編輯
vim harbor.yml

......
#hostname = 192.168.1.31
hostname = www.gerry.com
ui_url_protocol = https
ssl_cert = /data/cert/reg.edisonedu.com.crt
ssl_cert_key = /data/cert/reg.edisonedu.com.key
harbor_admin_password = admin
......

  以上只是一些最基本的配置,你還可以配置例如SMTP郵件的設置,可以自己去摸索。

  接下來就是執行准備這個harbor.cfg了,在harbor目錄下執行以下命令:

./prepare

  然后再執行install這個shell腳本進行install:

./install.sh

  它會經歷好幾個步驟:加載Harbor鏡像(初次安裝耗時較長)、准備運行環境、通過docker-compose啟動harbor(有興趣的童鞋可以看看harbor目錄下的docker-compose.yml文件)等。

  我們可以通過docker-compose ps命令查看啟動起來的docker實例:

 可以看到,整個harbor容器實例群包括了管理服務、數據庫服務、Job服務、日志服務以及Portal網頁入口(默認是80端口)服務等。

  為了能在你的開發機上能夠訪問到我們這個域名,你需要改一下Windows的hosts文件(C:/Windows/System32/drivers/etc/hosts),如果你用的阿里雲,那么你可能還需要開放一下端口號,80和443端口:

# your server ip
192.168.3.243 www.gerry.com

  這下我們可以在本地開發機上打開瀏覽器訪問reg.edisonedu.com了,可以看到Harbor的管理平台登錄頁面了:

  使用剛剛在配置文件里面配置的密碼登錄之后,可以看到如下管理界面:

  為了進行后面的演示,這里我們創建一個私有項目:

​ 然后再創建一個項目管理員用戶:

​ 最后,為test項目添加新創建的這個用戶作為項目管理員(由於我們后續會演示鏡像上傳,所以這里設為管理員,如果只是拉取鏡像,可以設為開發人員角色,如果只是看看那可以只設置為游客角色):

  接下來我們就會在另一台主機中訪問這台服務器上部署的Harbor私有鏡像倉庫了。

3.4 Docker主機訪問Harbor

  (1)首先,由於我們這里是自簽證書,不是受信任的,所以我們要做一些准備工作才能在普通主機上訪問到剛剛部署的Harbor鏡像倉庫。(注意:這一部分的操作在另外的一台主機上,非我們剛剛部署的Harbor的服務器上面)

  准備工作一:創建Harbor服務域名的證書文件夾

mkdir /etc/docker/certs.d/reg.edisonedu.com -p

  准備工作二:設置Hosts匹配我們設置的假域名

vim /etc/hosts

  加上一行:

47.22.232.200 reg.edisonedu.com #替換為你的Harbor服務器外網IP

  准備工作三:將Harbor服務器上的證書拷貝要訪問Harbor倉庫的主機上

  這一工作你可以選擇直接通過SFTP軟件將reg.edisonedu.com.crt從Harbor服務器上拷貝到客戶機剛剛創建的文件夾中(/etc/docker/certs.d/reg.edisonedu.com),也可以通過scp命令去拷貝,總之拷貝過來就行。這里我通過scp去拷貝:

scp root@47.22.232.200:/data/cert/reg.edisonedu.com.crt /etc/docker/certs.d/reg.edisonedu.com

  (2)其次,登錄到Harbor鏡像倉庫(由於Docker的默認源是docker hub,所以剛剛我們需要改host,這里需要登錄自己的源倉庫)

docker login reg.edisonedu.com

  (3)然后,就跟剛剛我們在docker hub中的步驟一樣了,假設我們要push一個鏡像到鏡像倉庫,首先打個Tag:

docker tag xdp-service-runtime:2.2 reg.edisonedu.com/test/xdp-service-runtime:2.2

  PS:這里我們打的tag加上了私有項目名test

  打完Tag,就可以push到鏡像倉庫了:

docker push reg.edisonedu.com/test/xdp-service-runtime:2.2

  推送完后,我們可以到Harbor的Web管理界面中驗證:

  (4)推送完之后,我們想在其他docker主機中pull下來呢?

docker pull reg.edisonedu.com/test/xdp-service-runtime:2.2

  (5)如果想退出我們的私有倉庫

docker logout reg.edisonedu.com

3.5 其他補充

  如果想要繼續更改harbor配置,那么改完后需要重新初始化Harbor:

docker-compose down -v # 暫停Harbor實例群
./prepare  # 生成配置文件,根據 harbor.cfg 配置生成docker-compose文件。
docker-compose up -d  # 后台啟動Harbor實例群

  想要暫停和重啟Harbor:

docker-compose  stop # 暫停 Harbor
docker-compose  start # 啟動 Harbor

  不用Harbor了,那么可以徹底刪除Harbor的數據和鏡像文件:

1
2
3
# 徹底地刪除 Harbor 的數據和鏡像
 rm -r /data/database
 rm -r /data/registry

(四)小結

 本文總結了流行的幾個鏡像倉庫的搭建步驟,並給出了基本使用示例。個人感覺:對於個人開發者或開源社區而言,docker hub主要提供的是類似於github的共享公共倉庫(當然docker hub也有提供私有倉庫)。對於小團隊而言,官方提供的Registry項目可以幫助小團隊快速地構建起自己的鏡像倉庫把精力更多放在快速迭代上面。而對於中大規模的團隊,Harbor的企業級特性更加適合此類型的團隊使用。

Docker-compose

安裝Compose

官方支持文檔 https://docs.docker.com/compose/

# 在線安裝(使用國內鏡像)
curl -L https://get.daocloud.io/docker/compose/releases/download/1.29.2/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

# 官方鏡像
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

# 離線安裝
# 官方網址 https://github.com/docker/compose/releases
# https://github.com/docker/compose/releases/download/1.29.2/docker-compose-Linux-x86_64,然后重新命名添加可執行權限即可:
mv docker-compose-linux-x86_64 /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

# 備注:docker-compose V1 和 V2 是由不同的語言編寫的。
# Docker Compose V2 是 Docker Compose 的主要版本。它已經在Golang中從頭開始完全重寫(V1在Python中)。V2 的安裝與 V1 不同。V2 不再是獨立的二進制文件,安裝腳本必須進行調整,某些命令是不同的。

# 目前V1最新穩定版本為 1.29.2。 目前建議安裝此版本。

# 卸載命令
sudo rm /usr/local/bin/docker-compose

docker-compose.yml配置詳解

DOCKER-COMPOPSE.YML 版本和DOCKER兼容性表

Compose file format Docker Engine release
Compose specification 19.03.0+
3.8 19.03.0+
3.7 18.06.0+
3.6 18.02.0+
3.5 17.12.0+
3.4 17.09.0+
3.3 17.06.0+
3.2 17.04.0+
3.1 1.13.1+
3.0 1.13.0+
2.4 17.12.0+
2.3 17.06.0+
2.2 1.13.0+
2.1 1.12.0+
2.0 1.10.0+

詳情請看官網文檔

頂級配置項

  • version 定義了版本信息
  • services 定義了服務的配置信息
  • networks 定義了網絡信息,提供給 services 中的 具體容器使用
  • volumes 定義了卷信息,提供給 services 中的 具體容器使用

示例:

version: "3.8"
services:
  redis: # 服務名稱
    image: redis:alpine # 使用的鏡像
    ports:
      - "6379" # 指定的端口
    networks:
      - frontend # 使用的網絡
    deploy:
      replicas: 2
      update_config:
        parallelism: 2
        delay: 10s
      restart_policy:
        condition: on-failure

  db:
    image: postgres:9.4
    volumes:
      - db-data:/var/lib/postgresql/data
    networks:
      - backend

  result:
    image: nginx
    ports:
      - "5001:80"
    networks:
      - backend
    depends_on:
      - db
    deploy:
      replicas: 1
      update_config:
        parallelism: 2
        delay: 10s
      restart_policy:
        condition: on-failure

  worker:
    image: nginx
    networks:
      - frontend
      - backend
    deploy:
      mode: replicated
      replicas: 1
      labels: [APP=VOTING]
      restart_policy:
        condition: on-failure
        delay: 10s
        max_attempts: 3
        window: 120s

networks:
  frontend:
  backend:
volumes:
  db-data:

SERVICES配置指令

1. container_name

指定容器名稱

version: "3"
services:

  redis:
    image: redis:alpine
    container_name: redis_test

2. image

指定為鏡像名稱或鏡像 ID。如果鏡像在本地不存在,Compose 將會嘗試拉取這個鏡像。

version: "3"
services:

  redis:
    image: redis:alpine

3. build

指定 Dockerfile 所在文件夾的路徑(可以是絕對路徑,或者相對 docker-compose.yml 文件的路徑)。 Compose 將會利用它自動構建這個鏡像,然后使用這個鏡像。

version: '3'
services:
  webapp:
    build: ./dir

也可以使用 context 指令指定 Dockerfile 所在文件夾的路徑(或者是git倉庫的URL)。同時使用 dockerfile 指令指定 Dockerfile 文件名。

version: '3'
services:

  webapp:
    build:
      context: ./dir
      dockerfile: Dockerfile-name

注意:
如果同時指定了 image和 build, image 不在具有單獨使用它的意義,而是指定了目前要構建的鏡像的名稱。 也就是說 Compose 會使用 build 指令中指定的 Dockerfile 構建的鏡像,之后構建的鏡像名稱使用 image 中指定的名字 webapp:tag命名。

4. command

使用 command 可以覆蓋容器啟動后默認執行的命令。

#寫成shell形式
command: bundle exec thin -p 3000
#寫成Dockerfile中的exec格式
command: [bundle, exec, thin, -p, 3000]

5. depends_on

解決容器的依賴、啟動先后的問題。

version: '3'

services:
  web:
    image: redis:alpine
    container_name: redis_test
    depends_on:
      - db

6. environment

設置環境變量。可以使用數組或字典兩種格式。

只給定名稱的變量會自動獲取運行 Compose 主機上的對應變量的信息。

environment:
  RACK_ENV: development
  SHOW: 'true'
  SESSION_SECRET:

environment:
  - RACK_ENV=development
  - SHOW=true
  - SESSION_SECRET

如果變量名稱或者值中用到 true|false,yes|no 等表達 布爾 含義的詞匯,最好放到引號里,避免 YAML 自動解析某些內容為對應的布爾語義。這些特定詞匯。

y|Y|yes|Yes|YES|n|N|no|No|NO|true|True|TRUE|false|False|FALSE|on|On|ON|off|Off|OFF

7. expose

暴露端口,但不映射到宿主機,只被連接的服務訪問。

僅可以指定容器內部的端口為參數

expose:
 - "3000"
 - "8000"

8. ports

映射端口信息。

宿主端口:容器端口 (即:HOST:CONTAINER) 的格式格式,或者僅僅指定容器的端口(宿主將會隨機選擇端口)。

ports:
 - "3000"
 - "3000-3005"
 - "8000:8000"
 - "9090-9091:8080-8081"
 - "49100:22"
 - "127.0.0.1:8001:8001"
 - "127.0.0.1:5000-5010:5000-5010"
 - "6060:6060/udp"

注意:當使用 HOST:CONTAINER 格式來映射端口時,如果你使用的容器端口小於 60 並且沒放到引號里,可能會得到錯誤結果,因為 YAML 會自動解析 xx:yy 這種數字格式為 60 進制。為避免出現這種問題,建議數字串都采用引號包括起來的字符串格式。

9. extra_hosts

類似 Docker 中的 –add-host 參數,指定額外的 host 名稱映射信息。會在啟動后的服務容器中 /etc/hosts 文件中添加host映射信息。

extra_hosts:
 - "somehost:162.242.195.82"
 - "otherhost:50.31.209.229"

10. networks

要加入的網絡,使用頂級 networks 定義下的條目 。

services:
  some-service:
    networks:
     - some-network
     - other-network
networks:
  some-network:
  other-network:

11. entrypoint

指定服務容器啟動后執行的入口文件。

12. user

指定容器中運行應用的用戶名。

13. working_dir

指定容器中工作目錄。

14. restart

指定容器退出后的重啟策略為始終重啟。該命令對保持服務始終運行十分有效,在生產環境 中推薦配置為 always 或者 unless-stopped 。

restart: always

15. alias

網絡上此服務的別名(備用主機名)。同一網絡上的其他容器可以使用服務名稱或此別名連接到其中一個服務的容器。
由於aliases是網絡范圍的,因此相同的服務可以在不同的網絡上具有不同的別名。
注意:網絡范圍的別名可以由多個容器共享,甚至可以由多個服務共享。如果是,則無法保證名稱解析為的容器。

services:
  some-service:
    networks:
      some-network:
        aliases:
         - alias1
         - alias3
      other-network:
        aliases:
         - alias2

VOLUMES配置指令

數據卷所掛載路徑設置。可以設置宿主機路徑 (HOST:CONTAINER) 或加上訪問模式 (HOST:CONTAINER:ro)。

該指令中路徑支持相對路徑。

volumes:
 - /var/lib/mysql
 - cache/:/tmp/cache
 - ~/configs:/etc/configs/:ro

1. 未顯式聲明網絡環境的docker-compose.yml

使用docker-compose up啟動容器后,這些容器都會被加入app_default網絡中。使用docker network ls可以查看網絡列表,docker network inspect 可以查看對應網絡的配置。

version: '3'
services:
  web:
    mage: nginx:latest
    container_name: web
    depends_on:
      - db
    ports:
      - "9090:80"
    links:
      - db
  db:
    image: mysql
    container_name: db

2. networks關鍵字指定自定義網絡

例如下面的docker-compose.yml文件,定義了front和back網絡,實現了網絡隔離。其中proxy和db之間只能通過app來實現通信。其中,custom-driver-1並不能直接使用,你應該替換為host, bridge, overlay等選項中的一種。

version: '3'

services:
  proxy:
    build: ./proxy
    networks:
      - front
  app:
    build: ./app
    networks:
      - front
      - back
  db:
    image: postgres
    networks:
      - back

networks:
  front:
    # Use a custom driver
    driver: custom-driver-1
  back:
    # Use a custom driver which takes special options
    driver: custom-driver-2
    driver_opts:
      foo: "1"
      bar: "2"

3. 配置默認網絡

version: '2'

services:
  web:
    build: .
    ports:
      - "8000:8000"
  db:
    image: postgres

networks:
  default:
    # Use a custom driver
    driver: custom-driver-1

4. 使用已存在的網絡

networks:
  default:
    external:
      name: my-pre-existing-network

Docker-Compose常用命令

  1. docker-compose up

用於部署一個 Compose 應用。

默認情況下該命令會讀取名為 docker-compose.yml 或 docker-compose.yaml 的文件。

當然用戶也可以使用 -f 指定其他文件名。通常情況下,會使用 -d 參數令應用在后台啟動。

  1. docker-compose stop

停止 Compose 應用相關的所有容器,但不會刪除它們。

被停止的應用可以很容易地通過 docker-compose restart 命令重新啟動。

  1. docker-compose rm

用於刪除已停止的 Compose 應用。

它會刪除容器和網絡,但是不會刪除卷和鏡像。

  1. docker-compose restart

重啟已停止的 Compose 應用。

如果用戶在停止該應用后對其進行了變更,那么變更的內容不會反映在重啟后的應用中,這時需要重新部署應用使變更生效。

  1. docker-compose ps

用於列出 Compose 應用中的各個容器。

輸出內容包括當前狀態、容器運行的命令以及網絡端口。

  1. docker-compose down

停止並刪除運行中的 Compose 應用。

它會刪除容器和網絡,但是不會刪除卷和鏡像。

Docker-Swarm

什么是Docker Swarm?

​ Swarm是Docker的一個編排工具,在之前我們只是在一台機器來進行docker的管理,但是有時容器並不一定都在一台主機上,如果是分布式的處於多台主機上,這時就可以借助於Swarm,Swarm是Docker自帶的編排工具,只要你安裝了Docker就會存在Docker Swarm工具。

  Swarm中的模式是有兩大類節點,一類是manager節點,另一類是worker節點,manager節點相當於對服務的創建和調度,worker節點主要是運行容器服務(當然manager節點也是可以運行的)。

​ manager節點狀態、信息的同步根據Raft consensus group的網絡進行通信同步的,而worker之間的通信依靠的是Gossip network做到的。

​ 另外一個重要的概念就是service,我們可以在一個manager節點上創建一個服務,但是可以根據這一個服務創建多個容器的任務,這些容器任務運行在不同的節點上。

​ 那么如何進行Swarm集群的搭建呢?

  • 在swarm模式下初始化一個Swarm集群,並且將當前主機加入到該集群作為manager節點
  • 加入其它節點到該集群中
  • 部署service到該集群中

Docker Swarm集群搭建

(一)創建多節點集群

1、查看Swarm命令
[root@centos-7 ~]# docker swarm --help

Usage:    docker swarm COMMAND

Manage Swarm

Commands:
  ca          Display and rotate the root CA
  init        Initialize a swarm
  join        Join a swarm as a node and/or manager
  join-token  Manage join tokens
  leave       Leave the swarm
  unlock      Unlock swarm
  unlock-key  Manage the unlock key
  update      Update the swarm

Run 'docker swarm COMMAND --help' for more information on a command.

可以看到其中有一個 init的命令,這就是初始化一個Swarm,我們再看看這個init的參數:

[root@centos-7 ~]# docker swarm init --help

Usage:    docker swarm init [OPTIONS]

Initialize a swarm

Options:
      --advertise-addr string                  Advertised address (format: <ip|interface>[:port])
      --autolock                               Enable manager autolocking (requiring an unlock
                                               key to start a stopped manager)
      --availability string                    Availability of the node
                                               ("active"|"pause"|"drain") (default "active")
      --cert-expiry duration                   Validity period for node certificates
                                               (ns|us|ms|s|m|h) (default 2160h0m0s)
      --data-path-addr string                  Address or interface to use for data path traffic
                                               (format: <ip|interface>)
      --data-path-port uint32                  Port number to use for data path traffic (1024 -
                                               49151). If no value is set or is set to 0, the
                                               default port (4789) is used.
      --default-addr-pool ipNetSlice           default address pool in CIDR format (default [])
      --default-addr-pool-mask-length uint32   default address pool subnet mask length (default 24)
      --dispatcher-heartbeat duration          Dispatcher heartbeat period (ns|us|ms|s|m|h)
                                               (default 5s)
      --external-ca external-ca                Specifications of one or more certificate signing
                                               endpoints
      --force-new-cluster                      Force create a new cluster from current state
      --listen-addr node-addr                  Listen address (format: <ip|interface>[:port])
                                               (default 0.0.0.0:2377)
      --max-snapshots uint                     Number of additional Raft snapshots to retain
      --snapshot-interval uint                 Number of log entries between Raft snapshots
                                               (default 10000)
      --task-history-limit int                 Task history retention limit (default 5)

可以看到它有很多的參數,其中第一個就是將本機的地址添加進去,讓自己成為一個Manager節點,這樣其它的節點都可以知道這個節點的存在。

2、創建一個Manager節點

在此之前我們應該先知道自己本機的ip,以便於–advertise-addr string 這個參數使用,查看本機ip:

然后就可以初始化Manager節點:

[root@centos-7 ~]# docker swarm init --advertise-addr=192.168.0.108
Swarm initialized: current node (sz7bfcmk637qhqfvzqhdnk3t2) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-0zzpk3xqq2ykb5d5jcd6hqeimckhrg5qu82xs7nw2wwnwyjmzg-9tmof9880m9r92vz5rygaa42e 192.168.0.108:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

可以看到已經創建了一個swarm manager節點–advertise-addr參數的值192.168.0.108是自己的ip,相當於將自己加入到swarm中。它也說明了其它worker加入的方式。

3、創建一個worker節點

另外再開啟一台虛擬機,將這台機器加入到swarm中成為worker節點:

[root@localhost ~]#  docker swarm join --token SWMTKN-1-0zzpk3xqq2ykb5d5jcd6hqeimckhrg5qu82xs7nw2wwnwyjmzg-9tmof9880m9r92vz5rygaa42e 192.168.0.108:2377
This node joined a swarm as a worker.

可以看到已經成功了。

4、查看節點狀態

注意的是查看節點狀態只能在manager節點那台機器上查看,普通節點無法執行查看命令:

[root@centos-7 ~]# docker node ls
ID                            HOSTNAME                STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
sz7bfcmk637qhqfvzqhdnk3t2 *   centos-7                Ready               Active              Leader              19.03.5
89tezea9ltn6mqek7b48gtve7     localhost.localdomain   Ready               Active                                  18.09.7

可以看到有兩台機器了。

(二)多節點容器創建

上面我們已經將節點創建好了,現在可以在這些節點上創建容器了,那么就需要看看docker service 的幫助信息:

[root@centos-7 ~]# docker service --help

Usage:    docker service COMMAND

Manage services

Commands:
  create      Create a new service
  inspect     Display detailed information on one or more services
  logs        Fetch the logs of a service or task
  ls          List services
  ps          List the tasks of one or more services
  rm          Remove one or more services
  rollback    Revert changes to a service's configuration
  scale       Scale one or multiple replicated services
  update      Update a service

Run 'docker service COMMAND --help' for more information on a command.

可以看到有create這個命令,可以這樣理解,docker service create 相當於docker run 就是創建容器,只不過在swarm中是不同的表現方式。

1、manager節點上創建容器
[root@centos-7 docker]# docker service create --name nginx-demo nginx
image busybox:latest could not be accessed on a registry to record
its digest. Each node will access busybox:latest independently,
possibly leading to different nodes running different
versions of the image.

edqgwqjka7ceyym9tg4gr9uim
overall progress: 1 out of 1 tasks 
1/1: running   
verify: Service converged 

可以查看是否創建成功:

[root@centos-7 docker]# docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
edqgwqjka7ce        demo                replicated          1/1                 busybox:latest      

可以具體看這個service的內容:

[root@centos-7 docker]# docker service ps demo
ID                  NAME                IMAGE               NODE                    DESIRED STATE       CURRENT STATE           ERROR    PORTS
lshgq4gfi3cv        demo.1              busybox:latest      localhost.localdomain   Running             Running 2 minutes ago   
2、水平擴展
  • 水平擴展

之前接觸或scale,它可以幫助我們創建多個service,這里也是可行的:

[root@centos-7 docker]# docker service scale demo=5
demo scaled to 5
overall progress: 5 out of 5 tasks 
1/5: running   
2/5: running   
3/5: running   
4/5: running   
5/5: running   
verify: Service converged 

已經有5個demo服務的容器正在運行了。可以先看看serverice:

[root@centos-7 docker]# docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
edqgwqjka7ce        demo                replicated          5/5                 busybox:latest      

再看看具體的容器數量:

[root@centos-7 docker]# docker service ps demo
ID                  NAME      IMAGE               NODE                    DESIRED STATE       CURRENT STATE       ERROR   PORTS
lshgq4gfi3cv        demo.1   busybox:latest      localhost.localdomain   Running             Running 3 hours ago                       
vvxl3f5p1w40        demo.2   busybox:latest      centos-7                Running             Running 3 hours ago                       
q5imz8pqygbv        demo.3   busybox:latest      centos-7                Running             Running 3 hours ago                       
ih6e2bhzzru2        demo.4   busybox:latest      localhost.localdomain   Running             Running 3 hours ago                       
l32ziu7ygoqw        demo.5   busybox:latest      centos-7                Running             Running 3 hours ago  

可以看到5個容器在不同的節點上,其中有3個容器在manager節點,有兩個在worker節點上。

當然,每一個節點都可以自己查看自己目前運行的容器,比如worker節點查看自己運行容器的數量:

[root@centos-7 docker]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS         PORTS        NAMES
fbf77922d24c        busybox:latest      "/bin/sh -c 'while t…"   3 hours ago         Up 3 hours              demo.5.l32ziu7ygoqwapu4rbazqhyej
51e0e953688f        busybox:latest      "/bin/sh -c 'while t…"   3 hours ago         Up 3 hours              demo.3.q5imz8pqygbv5fw1r2wyt84ps
6f2b6d5f670c        busybox:latest      "/bin/sh -c 'while t…"   3 hours ago         Up 3 hours              demo.2.vvxl3f5p1w40oc82wvm2yq01q

只需要通過docker ps命令即可。

  • 修復

scale在swarm中除了水平擴展外,還有一個作用,那就是修復作用,比如將某一個節點中的容器刪除掉,那么很快swarm中發覺並進行修復,數量保持原先的樣子。

假如現在刪除worker中的一個容器:

[root@localhost ~]# docker rm -f a8c8e6a95978
a8c8e6a95978

我們在manager節點中查看容器情況:

[root@centos-7 docker]# docker service ps demo
ID            NAME        IMAGE               NODE               DESIRED STATE   CURRENT STATE       ERROR      PORTS
lshgq4gfi3cv  demo.1     busybox:latest      localhost.localdomain   Running     Running 3 hours ago                                             
vvxl3f5p1w40  demo.2     busybox:latest      centos-7                Running     Running 3 hours ago                                             
q5imz8pqygbv  demo.3     busybox:latest      centos-7                Running    Running 3 hours ago                                             
zkoywf8h19p1  demo.4     busybox:latest      localhost.localdomain   Running   Starting less than a second ago                                 
ih6e2bhzzru2 \_ demo.4  busybox:latest      localhost.localdomain   Shutdown  Failed 23 seconds ago  "task: non-zero exit (137)"   
l32ziu7ygoqw  demo.5     busybox:latest      centos-7                Running    Running 3 hours ago                                             

可以看到紅色就是我們刪掉的容器,但是后面它又馬上補充了一個,保持了整個系統的穩定性。

wordpress部署

(一)單容器啟動方式

  如何使用Swarm進行部署wordpress呢?wordpress涉及到數據庫容器以及wordpress應用容器,那么這兩個容器很有可能是不在一台機器的,也就是使用Swarm中兩個容器是分配到不同的節點之上的。

  首先,將之前的service進行移除:

[root@centos-7 docker]# docker service rm demo
demo
1、創建overlay網絡

需要將這些service部署在一個overlay網絡中,所以需要有個overlay網絡。所以現在manager節點上創建這個網絡:

[root@centos-7 docker]# docker network create -d overlay demo
xcjljjqcw26b2xukuirgpo6au

可以查看:

[root@centos-7 docker]# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
xcjljjqcw26b        demo                overlay             swarm

值得注意的是其他節點上現在並沒有這個網絡,那么其它節點又是怎么連上這個網絡的呢?

2、啟動數據庫服務
[root@centos-7 docker]# docker service create --name mysql --env MYSQL_ROOT_PASSWORD=root --env MYSQL_DATABASE=wordpress --network demo --mount type=volume,source=mysql-data,destination=/var/lib/mysql mysql:5.7.24
eirarnex6suqa4uqzgowncigv
overall progress: 1 out of 1 tasks 
1/1: running   [==================================================>] 
verify: Service converged 

可以看看這個服務的詳情以及位於哪一個節點上:

[root@centos-7 docker]# docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
eirarnex6suq        mysql               replicated          1/1                 mysql:5.7.24        
[root@centos-7 docker]# docker service ps mysql
ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE     ERROR      PORTS
c4a4lggjmvuf        mysql.1             mysql:5.7.24        centos-7            Running             Running about a minute ago   
3、啟動wordpress服務

接着在manager節點上啟動wordpress服務

[root@centos-7 docker]#  docker service create --name wordpress -p 80:80 --env WORDPRESS_DB-PASSWORD=root \
 --env WORDPRESS_DB_HOST=mysql --network demo \
 --mount type=volume,source=wordpress-config,destination=/var/www/html wordpress

上述中 –mount參數將容器中的配置等一系列的文件映射出來了,是方便修改容器中配置文件,修復數據庫連接不上的錯誤,詳情查看:

4、測試

現在可以進行訪問本機的80端口進行測試了

(二)Docker Stack部署

這種部署方式就是使用docker-compose.yml文件來進行部署應用,而上面那種方式就是每次手動單啟容器。

  • 查看docker stack的命令
[root@centos-7 wordpress-stack]# docker stack --help

Usage:    docker stack [OPTIONS] COMMAND

Manage Docker stacks

Options:
      --orchestrator string   Orchestrator to use (swarm|kubernetes|all)

Commands:
  deploy      Deploy a new stack or update an existing stack
  ls          List stacks
  ps          List the tasks in the stack
  rm          Remove one or more stacks
  services    List the services in the stack

Run 'docker stack COMMAND --help' for more information on a command.

可以看到docker stack 的命令不是很多,第一個就是部署服務,一個docker-compose.yml就是一個服務,當然這個文件里面可能包含多個service。下面以實例來說明:

  • docker-compose.yml文件
version: '3'

services:

  web:
    image: wordpress
    ports:
      - 8080:80
    environment:
      WORDPRESS_DB_HOST: mysql
      WORDPRESS_DB_PASSWORD: root
    networks:
      - my-network
    depends_on:
      - mysql
    deploy:
      mode: replicated
      replicas: 3      #開啟3個web服務
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
      update_config:
        parallelism: 1
        delay: 10s

  mysql:
    image: mysql:5.7.24
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: wordpress
    volumes:
      - mysql-data:/var/lib/mysql
    networks:
      - my-network
    deploy:
      mode: global  #只能有一個mysql的服務
      placement:
        constraints:
          - node.role == manager #服務只能部署在manager節點上

volumes:
  mysql-data:

networks:
  my-network:
    driver: overlay   #注意這里可能服務在不同的主機上,需要使用overlay網絡
  • 部署wordpress stack
[root@centos-7 wordpress-stack]# docker stack deploy wordpress --compose-file=docker-compose.yml
Creating network wordpress_my-network
Creating service wordpress_web
Creating service wordpress_mysql
  • 查看stack
[root@centos-7 wordpress-stack]# docker stack ls
NAME                SERVICES            ORCHESTRATOR
wordpress           2                   Swarm
  • 查看stack中的服務
[root@centos-7 wordpress-stack]# docker stack ps wordpress
  • 移除stack
[root@centos-7 wordpress-stack]# docker stack rm wordpress
Removing service wordpress_mysql
Removing service wordpress_web
Removing network wordpress_my-network

Docker 安裝高並發組件

(一)Redis

1、獲取 redis 鏡像

docker pull redis

2、查看本地鏡像

docker images

3、從官網獲取 redis.conf 配置文件

cd /usr/local/docker   //進入目錄
wget http://download.redis.io/redis-stable/redis.conf   //下載redis配置文件
vim redis.conf  //修改配置文件
  • bind 127.0.0.1 => bind 0.0.0.0 #這是限制redis只能本地訪問
  • protected-mode no #默認yes,開啟保護模式,限制為本地訪問
  • daemonize no #默認no,改為yes意為以守護進程方式啟動,可后台運行,除非kill進程(可選),改為yes會使配置文件方式啟動redis失敗
  • dir ./ #輸入本地redis數據庫存放文件夾(可選)
  • appendonly yes #redis持久化(可選)

3、docker 啟動 redis

docker run -p 6379:6379 --name redis -v /root/gerry/redis/redis.conf:/etc/redis/redis.conf -v /root/gerry/redis/data:/data -d redis redis-server /etc/redis/redis.conf --appendonly yes
  • -p 6380:6380 端口映射:前表示主機部分,:后表示容器部分。
  • –name myredis 指定該容器名稱,查看和進行操作都比較方便。
  • -v 掛載目錄,規則與端口映射相同。
  • -d redis 表示后台啟動redis
  • redis-server /etc/redis/redis.conf 以配置文件啟動redis,加載容器內的conf文件,最終找到的是掛載的目錄/usr/local/docker/redis.conf
  • appendonly yes 開啟redis 持久化

4、查看redis狀態

5、進入redis

docker exec -it redis /bin/bash

(二)MongoDB

查詢mongo鏡像

docker search mongo

拉取鏡像

docker pull mongo

運行容器

docker run --name mongodb -p 27017:27017 -v $PWD/db:/data/db -d mongo:latest

1.以 admin 用戶身份進入mongo :

docker exec -it  mongodb  mongo admin

2.創建一個 admin 管理員賬號 :

db.createUser({ user: 'admin', pwd: 'admin123456', roles: [ { role: "userAdminAnyDatabase", db: "admin" } ] });

3.對 admin 用戶 進行身份認證

db.auth("admin","admin123456");

4.創建 用戶、密碼和數據庫

用戶zero
密碼123456
數據庫app

db.createUser({ user: 'zero', pwd: '123456', roles: [ { role: "readWrite", db: "app" } ] });

5.對 zero 進行身份認證

db.auth("zero","123456");

6.切換數據庫

use app

7.添加數據

向表test中添加數據

db.test.save({name:"zhangsan"});

8.查詢數據

db.test.find();

(三)RabbitMQ

查看倉庫里的RabbitMQ

docker search rabbitmq

拉取RabbitMQ

docker pull rabbitmq

這里是直接安裝最新的,如果需要安裝其他版本在rabbitmq后面跟上版本號即可

啟動RabbitMQ

docker run -d --name rabbit -p 15672:15672 -p 5672:5672 rabbitmq

安裝插件

先執行docker ps 拿到當前的鏡像ID
進入容器
安裝插件
ctrl+p+q退出當前容器
docker ps 
docker exec -it 鏡像ID /bin/bash
rabbitmq-plugins enable rabbitmq_management

訪問驗證

(四)Kafka

第一步 拉去zookeeper鏡像

docker pull wurstmeister/zookeeper

第二步 拉去kafka鏡像

docker pull wurstmeister/kafka

第三步 后台啟動zookeeper

docker run -d --name zookeeper -p 2181:2181 -v /etc/localtime:/etc/localtime wurstmeister/zookeeper

-v /etc/localtime:/etc/localtime 容器時間同步虛擬機的時間

第四步 后台啟動kafka

docker run -d --name kafka -p 9092:9092 -e KAFKA_BROKER_ID=0 -e KAFKA_ZOOKEEPER_CONNECT=192.168.3.249/kafka -e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://192.168.3.249:9092 -e KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9092 -v /etc/localtime:/etc/localtime wurstmeister/kafka

-e KAFKA_BROKER_ID=0 在kafka集群中,每個kafka都有一個BROKER_ID來區分自己

-e KAFKA_ZOOKEEPER_CONNECT=宿主機ip地址:2181/kafka 配置zookeeper管理kafka的路徑宿主機ip地址:2181/kafka

-e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://宿主機ip地址:9092 把kafka的地址端口注冊給zookeeper

-e KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9092 配置kafka的監聽端口

一直啟動失敗

問題解決
使用 -it 查看容器內部

docker run -it –name kafka -p 9092:9092 -e KAFKA_BROKER_ID=0 -e KAFKA_ZOOKEEPER_CONNECT=宿主機ip地址7/kafka -e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://宿主機ip地址:9092 -e KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9092 -v /etc/localtime:/etc/localtime wurstmeister/kafka
發現啟動內存不足

使用如下命令
docker run -d –name kafka -p 9092:9092 -e KAFKA_BROKER_ID=0 -e KAFKA_ZOOKEEPER_CONNECT=192.168.3.249/kafka -e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://192.168.3.249:9092 -e KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9092 -e KAFKA_HEAP_OPTS=“-Xmx256m -Xms256m” -v /etc/localtime:/etc/localtime wurstmeister/kafka

-e KAFKA_HEAP_OPTS=“-Xmx256m -Xms256m” 配置容器的啟動所用內存
默認的是KAFKA_HEAP_OPTS=“-Xmx1G -Xms1G”

此時啟動成功

(五)Nginx

拉取Nginx鏡像

docker pull nginx

運行Nginx容器

docker run -d --name mynginx -p 80:80 -v /root/gerry/nginx/nginx.conf:/etc/nginx/nginx.conf -v /root/gerry/nginx/logs:/var/log/nginx -v /root/gerry/nginx/html:/usr/share/nginx/html -v /gerry/nginx/conf:/etc/nginx/conf.d --privileged=true nginx

測試nginx部署結果

(六)Elasticsearch

拉取鏡像

docker pull docker.elastic.co/elasticsearch/elasticsearch:7.14.0

啟動elasticsearch

docker run -d -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:7.14.0

Docker Jenkins

前言

  有人問,為什么要用Jenkins?我說下我以前開發的痛點,在一些中小型企業,每次開發一個項目完成后,需要打包部署,可能沒有專門的運維人員,只能開發人員去把項目打成一個war包,可能這個項目已經上線了,需要把服務關,在部署到服務器上,將項目啟動起來,這個時候可能某個用戶正在操作某些功能上的東西,如果你隔三差五的部署一下,這樣的話對用戶的體驗也不好,自己也是煩的很,總是打包拖到服務器上。希望小型企業工作人員學習一下,配置可能復雜,但是你配置好了之后,你只需要把代碼提交到Git或者Svn上,自動構建部署,非常方便。

Jenkins簡介

  Jenkins是一個開源軟件項目,是基於Java開發的一種持續集成工具,用於監控持續重復的工作,旨在提供一個開放易用的軟件平台,使軟件的持續集成變成可能。

jenkins基本工作原理

![img](data:image/jpg;base64,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BAgQIECAAAECBAgQKC7w2WefLR599NGjgCJuxzITgX0ICCf2oWwfBAgQIECAAAECBAgQGIHA008/fRROxG0TgX0JCCf2JW0/BAgQIECAAAECBAgQKC5w9erVo3AibpsI7EtAOLEvafshQIAAAQIECBAgQIDACAT8SscIijTBLgonJlhUh0SAAAECBAgQIECAAIFtBc6fP7+IPxOBfQoIJ/apbV8ECBAgQIAAAQIECBAoLvDee+8t4s9EYJ8Cwol9atsXAQIECBAgQIAAAQIEigv8+9//XsSficA+BYQT+9S2LwIECBAgQIAAAQIEJiNw8eLFoy+PzO9pMH9kNCZRP1MdAeFEnVroCQECBAgQIECAAAECIxIQRIwniFhVqxE93SbfVeHE5EvsAAkQIECAAAECBAgQ2IVADnh30bY2dyugdrv13aZ14cQ2arYhQIAAAQIECBAgQGD2Aga4430KqF292gkn6tVEjwgQIECAAAECBAgQGIGAAe4IirSii2q3AuaAi4UTB8S3awIECBAgQIAAAQIExitggKt24xWo13PhRL2a6BEBAgQIECBAgAABAiMQEE6MoEgruqh2K2AOuFg4cUB8uyZAgAABAgQIECBAYLwCBrhqN16Bej0XTtSriR4RIECAAAECBAgQIDACAeHECIq0ootqtwLmgIuFEwfEt2sCBAgQIECAAAECBMYrYICrduMVqNdz4US9mugRAQIECBAgQIAAAQIjEBBOjKBIK7qoditgDrhYOHFAfLsmQIAAAQIECBAgQGC8Aga4ajdegXo9F07Uq4keESBAgAABAgQIECAwAoFDhxP37t1bvPLKK4sPPvhgcf369UX2J+ZPPPFEtzwYb9++vbh06dID82W8sU78Xbx4cRFt96dYFo+1+8nbub+HrZPrxj4OPWVfDt0P+78vIJy4b+EWAQIECBAgQIAAAQIE1haoMMDNgOL111/vgoXs/JUrVxY3btzoln344YfdPNaNIOPChQtdUJHr5jy2yaAjAoS4HaFDHmcbWkQY8sYbb+SmR/PsT2y7bIrthBPLZCwTTngOECBAgAABAgQIECBAYAuBHLRvsempb5KD/ggM4m9VOBE7jnUjzGiDhzyWnOeVELF+hAmxTd7Oddp5BhXCiY7Jf7YQEE5sgWYTAgQIECBAgAABAgQI5OD8UBLtVRCbhBOr+hsBQ4YQ7Tr9cKK/Ttxvw4lVH/1IL1dOtLpup4BwIiXMCRAgQIAAAQIECBAgsIFADrY32OTUV41gIMKAt99+u7vCIUKC+Ft25cTVq1e7j2i0H89oO7RJOJHfYxFXSvTDifwejLbtvB3rCidSw7wVEE60Gm4TIECAAAECBAgQIEBgTYEK4UR2NQf9DwsnIkjIUOHmzZuL8+fPH32fRNyOQCPa6U/LrpzIdoQTfS33txUQTmwrZzsCBAgQIECAAAECBGYtUCGciJAgroR46623jq6ciIBh2ZUTbTgRt2OKdSPQ6N/uFnz+n03DCR/raPXcXldAOLGulPUIECBAgAABAgQIECDQCFQIJ+J7J/KKh/g4RX6k4qRwIn7JI/7acCLbag6xuxnhRBxrXF0RwUNs067bthGhR/ah307cj3V9rGOZjGXCCc8BAgQIECBAgAABAgQIbCFQIZzIj3FE92PgnyFCXFGRU/sRjLgd4UGEDBEwtMFCXoWR67/55pvdL3rEunmlRbQZt/PqiH7QIJxIdfNNBYQTm4pZnwABAgQIECBAgAABAovF0fc1HBIjrpDIkCGubIhgIe5fuHChux19y7AhgoO4HetFKBFTBhoRakTQEWFDzLPd/rFF2/Ezo/lRkJi3PzvaBhcZ3vTn/UCjv4993M8+7WNf9rGegHBiPSdrESBAgAABAgQIECBA4AGBQw9wIwiIgX5830T/6oYIHfIKiehnBgIRLkTwGRG4yQAABuBJREFUkCFFGyzEweXyfnvxWLSRAUgLEdtEGBJtu3KilXF7EwHhxCZa1iVAgAABAgQIECBAgMDnAocOJxRiewG1295uV1sKJ3Ylq10CBAgQIECAAAECBCYtYIA73vKqXb3aCSfq1USPCBAgQIAAAQIECBAYgYAB7giKtKKLarcC5oCLhRMHxLdrAgQIECBAgAABAgTGK2CAq3bjFajXc+FEvZroEQECBAgQIECAAAECIxAQToygSCu6qHYrYA64WDhxQHy7JkCAAAECBAgQIEBgvAIGuGo3XoF6PRdO1KuJHhEgQIAAAQIECBAgMAIB4cQIirSii2q3AuaAi4UTB8S3awIECBAgQIAAAQIExitggKt24xWo13PhRL2a6BEBAgQIECBAgAABAiMQEE6MoEgruqh2K2AOuFg4cUB8uyZAgAABAgQIECBAYLwCOcA1f2QxVoPxPvum13PhxPRq6ogIECBAgAABAgQIENiDwMWLF0c7KB9rmHCa/Y76meoICCfq1EJPCBAgQIAAAQIECBAgQIDALAWEE7Msu4MmQIAAAQIECBAgQIAAAQJ1BIQTdWqhJwQIECBAgAABAgQIECBAYJYCwolZlt1BEyBAgAABAgQIECBAgACBOgLCiTq10BMCBAgQIECAAAECBAgQIDBLAeHELMvuoAkQIECAAAECBAgQIECAQB0B4USdWugJAQIECBAgQIAAAQIECBCYpYBwYpZld9AECBAgQIAAAQIECBAgQKCOgHCiTi30hAABAgQIECBAgAABAgQIzFJAODHLsjtoAgQIECBAgAABAgQIECBQR0A4UacWekKAAAECBAgQIECAAAECBGYpIJyYZdkdNAECBAgQIECAAAECBAgQqCMgnKhTCz0hQIAAAQIECBAgQIAAAQKzFBBOzLLsDpoAAQIECBAgQIAAAQIECNQREE7UqYWeECBAgAABAgQIECBAgACBWQoIJ2ZZdgdNgAABAgQIECBAgAABAgTqCAgn6tRCTwgQIECAAAECBAgQIECAwCwFhBOzLLuDJkCAAAECBAgQIECAAAECdQSEE3VqoScECBAgQIAAAQIECBAgQGCWAsKJWZbdQRMgQIAAAQIECBAgQIAAgToCwok6tdATAgQIECBAgAABAgQIECAwSwHhxCzL7qAJECBAgAABAgQIECBAgEAdAeFEnVroCQECBAgQIECAAAECBAgQmKWAcGKWZXfQBAgQIECAAAECBAgQIECgjoBwok4t9IQAAQIECBAgQIAAAQIECMxSQDgxy7I7aAIECBAgQIAAAQIECBAgUEdAOFGnFnpCgAABAgQIECBAgAABAgRmKSCcmGXZHTQBAgQIECBAgAABAgQIEKgjIJyoUws9IUCAAAECBAgQIECAAAECsxQQTsyy7A6aAAECBAgQIECAAAECBAjUERBO1KmFnhAgQIAAAQIECBAgQIAAgVkKCCdmWXYHTYAAAQIECBAgQIAAAQIE6ggIJ+rUQk8IECBAgAABAgQIECBAgMAsBYQTsyy7gyZAgAABAgQIECBAgAABAnUEhBN1aqEnBAgQIECAAAECBAgQIEBglgLCiVmW3UETIECAAAECBAgQIECAAIE6AsKJOrXQEwIECBAgQIAAAQIECBAgMEsB4cQsy+6gCRAgQIAAAQIECBAgQIBAHQHhRJ1a6AkBAgQIECBAgAABAgQIEJilgHBilmV30AQIECBAgAABAgQIECBAoI6AcKJOLfSEAAECBAgQIECAAAECBAjMUkA4McuyO2gCBAgQIECAAAECBAgQIFBHQDhRpxZ6QoAAAQIECBAgQIAAAQIEZikgnJhl2R00AQIECBAgQIAAAQIECBCoIyCcqFMLPSFAgAABAgQIECBAgAABArMUEE7MsuwOmgABAgQIECBAgAABAgQI1BEQTtSphZ4QIECAAAECBAgQIECAAIFZCggnZll2B02AAAECBAgQIECAAAECBOoICCfq1EJPCBAgQIAAAQIECBAgQIDALAWEE7Msu4MmQIAAAQIECBAgQIAAAQJ1BIQTdWqhJwQIECBAgAABAgQIECBAYJYCwolZlt1BEyBAgAABAgQIECBAgACBOgLCiTq10BMCBAgQIECAAAECBAgQIDBLAeHELMvuoAkQIECAAAECBAgQIECAQB0B4USdWugJAQIECBAgQIAAAQIECBCYpYBwYpZld9AECBAgQIAAAQIECBAgQKCOgHCiTi30hAABAgQIECBAgAABAgQIzFJAODHLsjtoAgQIECBAgAABAgQIECBQR0A4UacWekKAAAECBAgQIECAAAECBGYpIJyYZdkdNAECBAgQIECAAAECBAgQqCPw/6k0i2q4wJQLAAAAAElFTkSuQmCC)

准備工作

1.需要准備一台服務器,大家可以在網上買,個人學習的話還是建議大家去安裝一個虛擬機,去裝一個Linux系統

2.需要准備一個遠程連接工具,連接到Linux系統

3.機器上安裝了Docker

開始安裝

1.啟動docker,下載Jenkins鏡像文件

docker pull jenkins/jenkins

2.創建Jenkins掛載目錄並授權權限(我們在服務器上先創建一個jenkins工作目錄 /var/jenkins_mount,賦予相應權限,稍后我們將jenkins容器目錄掛載到這個目錄上,這樣我們就可以很方便地對容器內的配置文件進行修改。 如果我們不這樣做,那么如果需要修改容器配置文件,將會有點麻煩,因為雖然我們可以使用docker exec -it –user root 容器id /bin/bash 命令進入容器目錄,但是連簡單的 vi命令都不能使用)

1
2
mkdir -p /var/jenkins_mount
chmod 777 /var/jenkins_mount

3.創建並啟動Jenkins容器

  -d 后台運行鏡像

  -p 10240:8080 將鏡像的8080端口映射到服務器的10240端口。

  -p 10241:50000 將鏡像的50000端口映射到服務器的10241端口

  -v /var/jenkins_mount:/var/jenkins_mount /var/jenkins_home目錄為容器jenkins工作目錄,我們將硬盤上的一個目錄掛載到這個位置,方便后續更新鏡像后繼續使用原來的工作目錄。這里我們設置的就是上面我們創建的 /var/jenkins_mount目錄

  -v /etc/localtime:/etc/localtime讓容器使用和服務器同樣的時間設置。

  –name myjenkins 給容器起一個別名

docker run -d -p 10240:8080 -p 10241:50000 -v /var/jenkins_mount:/var/jenkins_home -v /etc/localtime:/etc/localtime --privileged=true --name myjenkins jenkins/jenkins

4.查看jenkins是否啟動成功,如下圖出現端口號,就為啟動成功了

docker ps -l

5.查看docker容器日志。

docker logs myjenkins

6.配置鏡像加速,進入 cd /var/jenkins_mount/ 目錄。

cd /var/jenkins_mount/

修改 vi hudson.model.UpdateCenter.xml里的內容

將 url 修改為 清華大學官方鏡像:https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json

7.訪問Jenkins頁面,輸入你的ip加上10240

8.管理員密碼獲取方法,編輯initialAdminPassword文件查看,把密碼輸入登錄中的密碼即可,開始使用。

vi /var/jenkins_mount/secrets/initialAdminPassword

9.到此以全部安裝成功,盡情的使用吧!

.NetCore源碼交給git管理(本地)

1、安裝git的客戶端工具,下載地址: https://git-scm.com/downloads

2、創建文件夾並初始化git倉庫

3、提交代碼到本地倉庫

Git本地倉庫的源碼推送至遠程的github

1、注冊一個github賬號【賬號和密碼要記得】

2、配置本地git與遠程git的互信關系

1
2
在本地生成一個密鑰對
ssh-keygen -t rsa  -C "inet_ygssoftware@163.com"

3、創建一個倉庫然后在Git倉庫配置推送所需信息

1
2
3
4
5
6
git config --global user.name 'gerry'
git config --global user.email 'inet_ygssoftware@163.com'
git config --global http.sslVerify "false"

#### git 
git remote set-url origin https://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@github.com/ygs12/repo-demo.git

4、本地倉庫源碼同步到Github

1
2
3
git add . ## 添加當前項目所有新的或者修改后的文件到暫存區
git commit -m "首次提交" ### 把所有暫存區的數據提交到本地倉
git push -u orgin master ### 首次推送的時候需要,后面直接寫git push即可

配Jenkins拉取Github中的源碼

1、創建“自由風格的項目”

2、配置私人密鑰

注意:首次生成的時候token能看見,但是后面就看不見了,生成后必須記錄下token一遍后面直接使用

3、配置github倉庫

基於gitee WebHook完成代碼提交就觸發Jenkins自動構建

1、在剛剛配置的項目中找到gitee的webhook地址

2、在gitee對應的項目中配置gitee的webhook

3、測試是否正確

配置Jenkins基於.NET項目構建鏡像並推送之Harbor倉庫

1、安裝Docker插件
2、開啟Docker的tcp訪問端口

vi /lib/systemd/system/docker.service
## 修改ExecStart
## 修改前
ExecStart=/usr/bin/dockerd -H fd:// --containerd /var/run/containerd/containerd.sock
## 修改后
ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock --containerd /var/run/containerd/containerd.sock
## 重新加載docker配置文件及重啟Docker
systemctl deamon-reload && systemctl restart docker

3、在Dokcer插件中配置連接2376端口

4、在我的視圖中配置使用Docker容器

5、查看鏡像是否推送到harbor倉庫

基於Jenkins拉取私服鏡像后在指定服務器構建容器

1、在部署的服務器的docker配置文件添加鏡像私服地址

{
  "registry-mirrors": [
        "https://registry.cn-hangzhou.aliyuncs.com",
        "https://ebkn7ykm.mirror.aliyuncs.com",
        "https://docker.mirrors.ustc.edu.cn",
        "http://f1361db2.m.daocloud.io",
        "https://registry.docker-cn.com"
    ],
    "insecure-registries": ["192.168.3.249"]
}

########## 重新加載配置文件和重啟Docker容器 ############
systemctl daemon-reload && systemctl restart docker

2、通過SSH插件配置部署服務的SSH

小結

以上內容,已把 Docker 容器的一些常用操作命令進行了演示,掌握之后,正常操作入門已是沒有太大問題。

在進行本次演練的過程中,我也是踩了不少的坑。比如自己構建鏡像的那一塊,我就是由於對 Dockerfile 的配置沒有寫正確,導致鏡像打包過程中總是報錯。后面還是向社區的朋友請教了之后才磕磕碰碰的發布成功。

^ _ ^ ,感謝 橙子! 這是他搞的一個開源項目,ccnetcore/Yi: Yi.Framework-基於.NET5+Vue快速開發框架 (github.com),大家感興趣的可以去瞅瞅!順便點個星星。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM