【学习笔记】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),大家感兴趣的可以去瞅瞅!顺便点个星星。

posted @ 2022-01-09 16:03  李恒  阅读(2864)  评论(0编辑  收藏  举报