Docker实战——项目容器化改造实战

What-什么是容器

容器是一种轻量级、可移植、自包含的软件打包技术,使应用程序可以在几乎任何地方以相同的方式运行。开发人员在自己笔记本上创建并测试好的容器,无需任何修改就能够在生产系统的虚拟机、物理服务器或公有云主机上运行。

容器与虚拟机

谈到容器,就不得不将它与虚拟机进行对比,因为两者都是为应用提供封装和隔离。

容器由两部分组成:

  1. 应用程序本身
  2. 依赖:比如应用程序需要的库或其他软件

容器在 Host 操作系统的用户空间中运行,与操作系统的其他进程隔离。这一点显著区别于的虚拟机。

传统的虚拟化技术,比如 VMWare, KVM, Xen,目标是创建完整的虚拟机。为了运行应用,除了部署应用本身及其依赖(通常几十 MB),还得安装整个操作系统(几十 GB)。

下图展示了二者的区别。

如图所示,由于所有的容器共享同一个 Host OS,这使得容器在体积上要比虚拟机小很多。另外,启动容器不需要启动整个操作系统,所以容器部署和启动速度更快,开销更小,也更容易迁移。

Why - 为什么需要容器?

为什么需要容器?容器到底解决的是什么问题?
简要的答案是:容器使软件具备了超强的可移植能力

容器解决的问题

我们来看看今天的软件开发面临着怎样的挑战?

如今的系统在架构上较十年前已经变得非常复杂了。以前几乎所有的应用都采用三层架构(Presentation/Application/Data),系统部署到有限的几台物理服务器上(Web Server/Application Server/Database Server)。

而今天,开发人员通常使用多种服务(比如 MQCacheDB)构建和组装应用,而且应用很可能会部署到不同的环境,比如虚拟服务器,私有云和公有云。

一方面应用包含多种服务,这些服务有自己所依赖的库和软件包;另一方面存在多种部署环境,服务在运行时可能需要动态迁移到不同的环境中。这就产生了一个问题:

如何让每种服务能够在所有的部署环境中顺利运行?

于是我们得到了下面这个矩阵:

各种服务和环境通过排列组合产生了一个大矩阵。开发人员在编写代码时需要考虑不同的运行环境,运维人员则需要为不同的服务和平台配置环境。对他们双方来说,这都是一项困难而艰巨的任务。

如何解决这个问题呢?

聪明的技术人员从传统的运输行业找到了答案。

几十年前,运输业面临着类似的问题。

每一次运输,货主与承运方都会担心因货物类型的不同而导致损失,比如几个铁桶错误地压在了一堆香蕉上。另一方面,运输过程中需要使用不同的交通工具也让整个过程痛苦不堪:货物先装上车运到码头,卸货,然后装上船,到岸后又卸下船,再装上火车,到达目的地,最后卸货。一半以上的时间花费在装、卸货上,而且搬上搬下还容易损坏货物。

这同样也是一个 NxM 的矩阵。

幸运的是,集装箱的发明解决这个难题。

任何货物,无论钢琴还是保时捷,都被放到各自的集装箱中。集装箱在整个运输过程中都是密封的,只有到达最终目的地才被打开。标准集装箱可以被高效地装卸、重叠和长途运输。现代化的起重机可以自动在卡车、轮船和火车之间移动集装箱。集装箱被誉为运输业与世界贸易最重要的发明。

Docker 将集装箱思想运用到软件打包上,为代码提供了一个基于容器的标准化运输系统。Docker 可以将任何应用及其依赖打包成一个轻量级、可移植、自包含的容器。容器可以运行在几乎所有的操作系统上。

其实,集装箱容器对应的英文单词都是 “Container”
容器是国内约定俗成的叫法,可能是因为容器比集装箱更抽象,更适合软件领域的原故吧。

我个人认为:在老外的思维中,“Container” 只用到了集装箱这一个意思,Docker Logo 不就是一堆集装箱吗?

Docker 的特性

我们可以看看集装箱思想是如何与 Docker 各种特性相对应的。

特性

集装箱

Docker

打包对象

几乎任何货物

任何软件及其依赖

硬件依赖

标准形状和接口允许集装箱被装卸到各种交通工具,整个运输过程无需打开

容器无需修改便可运行在几乎所有的平台上 -- 虚拟机、物理机、公有云、私有云

隔离性

集装箱可以重叠起来一起运输,香蕉再也不会被铁桶压烂了

资源、网络、库都是隔离的,不会出现依赖问题

自动化

标准接口使集装箱很容易自动装卸和移动

提供 run, start, stop 等标准化操作,非常适合自动化

高效性

无需开箱,可在各种交通工具间快速搬运

轻量级,能够快速启动和迁移

职责分工

货主只需考虑把什么放到集装箱里;承运方只需关心怎样运输集装箱

开发人员只需考虑怎么写代码;运维人员只需关心如何配置基础环境

容器的优势

对于开发人员Build Once, Run Anywhere

容器意味着环境隔离和可重复性。开发人员只需为应用创建一次运行环境,然后打包成容器便可在其他机器上运行。另外,容器环境与所在的 Host 环境是隔离的,就像虚拟机一样,但更快更简单。

对于运维人员Configure Once, Run Anything

只需要配置好标准的 runtime 环境,服务器就可以运行任何容器。这使得运维人员的工作变得更高效,一致和可重复。容器消除了开发、测试、生产环境的不一致性。

How - 容器是如何工作的?

从下节开始我们将学习容器核心知识的最主要部分。首先会介绍 Docker 的架构,然后分章节详细讨论 Docker 的镜像、容器、网络和存储。

Docker架构详解

Docker 的核心组件包括:

  1. Docker 客户端 - Client
  2. Docker 服务器 - Docker daemon
  3. Docker 镜像 - Image
  4. Registry
  5. Docker 容器 - Container

Docker 架构如下图所示:

Docker 采用的是 Client/Server 架构。客户端向服务器发送请求,服务器负责构建、运行和分发容器。客户端和服务器可以运行在同一个 Host 上,客户端也可以通过 socket REST API 与远程的服务器通信。

Docker 客户端

最常用的 Docker 客户端是 docker 命令。通过 docker 我们可以方便地在 Host 上构建和运行容器。

docker 支持很多操作(子命令),后面会逐步用到。

除了 docker 命令行工具,用户也可以通过 REST API 与服务器通信。

Docker 服务器

Docker daemon 是服务器组件,以 Linux 后台服务的方式运行。

Docker daemon 运行在 Docker host 上,负责创建、运行、监控容器,构建、存储镜像。

默认配置下,Docker daemon 只能响应来自本地 Host 的客户端请求。如果要允许远程客户端请求,需要在配置文件中打开 TCP 监听,步骤如下:

  1. 编辑配置文件 /etc/systemd/system/multi-user.target.wants/docker.service,在环境变量 ExecStart 后面添加 -H tcp://0.0.0.0,允许来自任意 IP 的客户端连接。



    如果使用的是其他操作系统,配置文件的位置可能会不一样。
  2. 重启 Docker daemon
     

  3. 服务器 IP 192.168.56.102,客户端在命令行里加上 -H 参数,即可与远程服务器通信。
     



    info 子命令用于查看 Docker 服务器的信息。

Docker 镜像

可将 Docker 镜像看着只读模板,通过它可以创建 Docker 容器。

例如某个镜像可能包含一个 Ubuntu 操作系统、一个 Apache HTTP Server 以及用户开发的 Web 应用。

镜像有多种生成方法:

  1. 可以从无到有开始创建镜像
  2. 也可以下载并使用别人创建好的现成的镜像
  3. 还可以在现有镜像上创建新的镜像

我们可以将镜像的内容和创建步骤描述在一个文本文件中,这个文件被称作 Dockerfile,通过执行 docker build <docker-file> 命令可以构建出 Docker 镜像,后面我们会讨论。

Docker 容器

Docker 容器就是 Docker 镜像的运行实例。

用户可以通过 CLIdocker)或是 API 启动、停止、移动或删除容器。可以这么认为,对于应用软件,镜像是软件生命周期的构建和打包阶段,而容器则是启动和运行阶段。

Registry

Registry 是存放 Docker 镜像的仓库,Registry 分私有和公有两种。

Docker Hubhttps://hub.docker.com/ 是默认的 Registry,由 Docker 公司维护,上面有数以万计的镜像,用户可以自由下载和使用。

出于对速度或安全的考虑,用户也可以创建自己的私有 Registry。后面我们会学习如何搭建私有 Registry

docker pull 命令可以从 Registry 下载镜像。
docker run 命令则是先下载镜像(如果本地没有),然后再启动容器。

docker私有镜像中心搭建

拉取registry2版本的镜像并启动镜像容器,-v设置映射地址(本机:容器),映射端口号

docker run -d -v /opt/registry:/var/lib/registry -p 5000:5000 registry:2

修改需要上传至私有镜像中心的 镜像名称

docker tag task-web-server:2.20.1 192.168.5.206:5000/dmp/task-web-server:2.20.1

红色:为镜像中心的insecure-registries的值

{
        "registry-mirrors": ["https://njrds9qc.mirror.aliyuncs.com"],
        "insecure-registries":["192.168.5.206:5000"]
}

修改完后需要重启docker

systemctl daemon-reload
systemctl restart docker

查看镜像中心的镜像

http://192.168.5.206:5000/v2/_catalog

或者用curl命令

curl -I -X DELETE http://192.168.5.206:5000/v2/_catalog/dmp/task-web-server

向镜像中心推送镜像

docker push 192.168.5.206:5000/dmp/task-web-server:2.20.1

镜像中心删除镜像

curl -I -X DELETE http://192.168.5.206:5000/v2/192.168.5.206:5000/dmp/streaming-web-server/manifests/sha256:011acab4bc9654776b05a26590ab23451bf1fec4fb859b74e3e995f7eb55f3d8

Dockerfile

Dockerfile命令详解

Dockerfile 文件常用命令说明

FROM

指定base镜像

MAINTAINER

设置镜像的作者,可以是任意字符串

COPY

将文件从build context复制到镜像

COPY支持两种形式:

  1. COPY src dest
  2. COPY ["src","dest"]

注意:src只能指定build context中的文件或目录

ADD

与COPY类似,从build context复制文件到镜像。不同的是如果src是归档文件(tar,zip,tgz,xz等),文件会被自动解压到dest。

ENV

设置环境变量,环境变量可被后面的指令使用。例如:

ENV MY_VERSION 1.3

RUN apt-get install -y mypackage=$MY_VERSION

EXPOSE

指定容器中的进程会监听某个端口,Docker可以将该端口暴露出来。我们会咋容器网络部分详细讨论。

VOLUME

将文件或目录生命为volume。

WORKDIR

为后面的RUN,CMD,ENTRYPOINT,ADD或COPY指定设置镜像中的当前工作目录

RUN

在容器中运行指定的命令

CMD

容器启动时运行指定的命令。

Dockerfile中可以有多个CMD指令,但只有最后一个生效。CMD可以被docker run之后的参数替换。

ENTRYPOINT

设置容器启动时运行的命令。

Dockerfile中可以有多个ENTRYPOINT指令,但只有最后一个生效。CMD或docker run之后的参数会被当做参数传递给ENTRYPOINT

上为一个简单的Dockerfile   创建了一个tmpfile1文件,并将tmpfile2复制进的镜像当前目录/testdir,并将bunch.tar.gz解压复制进了/testdir目录。

构建镜像

①构建前确保build context中存在需要的文件。

②依次执行Dockerfile指令,完成构建。

运行容器,验证镜像内容:

①进入容器,当前目录为WORKDIR,如果不存在,Docker会自动为我们创建。

②WORKDIR中保存了我们希望存在的目录

③ENV指令定义的环境变量已经生效。

通过Dockerfile构建镜像并启动容器

#构建task-web-server

docker build -t 192.168.5.206:5000/dmp/task-web-server:2.20.2 .

#容器启动命令

docker run -it -p 8077:8022 192.168.5.206:5000/dmp/task-web-server:2.20.2

docker自定义网络配置

docker network create --driver bridge my_net

brctl show

docker network inspect my_net

创建网桥时加入 网段subnet 网关gateway

docker network create --driver bridge --subnet 172.22.16.0/24 --gateway 172.22.16.1 my_net

容器启动时,通过--network指定网桥

docker run -it --network=my_net task-web-server:1.0.0

可以手动指定网段分配ip

docker run -it --network=my_net --ip 172.22.16.8 task-web-server:1.0.0

项目Docker架构猜想

Docker的CI猜想

引入Docker-Compose容器管理工具

Compose是一个用于定义和运行多容器Docker应用程序的工具。使用Compose,您可以使用YAML文件来配置应用程序的服务。然后,使用单个命令,您可以从配置中创建并启动所有服务。

使用docker Compose基本上是三步骤:

  1. 定义应用程序的Dockerfile文件
  2. 定义构成应用程序的服务,docker-compose.yml以便于可以在隔离环境中一起运行
  3. run docker-compose up和compose启动并运行整个应用程序。

Compose具有管理应用程序整个生命周期的命令:

  • 启动、停止和重建服务
  • 查看正在运行的服务的状态
  • 流式传输运行服务的日志输出
  • 在服务上运行一次行命令

在Linux系统中Compose的部署

安装Compose

  • 运行下命令以下载Docker Compose的当前稳定版本:

我对官网方法呵呵了,curl连接有问题。这里提供无坑方法

官网找到最新稳定版本:https://github.com/docker/compose/releases/ , 下载对应二进制文件,ftp到服务器的/usr/local/bin/目录下,并将文件名改为docker-compose。

  • 对二进制文件应用赋予root权限:

sudo chmod +x /usr/local/bin/docker-compose

卸载

卸载docker Compose:

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

YAML 配置命令

配置

说明

build        

指定 Dockerfile 所在的目录地址,用于构建镜像,并使用此镜像创建容器,比如上面配置的build: .

command

容器的执行命令

dns

自定义 dns 服务器

expose

暴露端口配置,但不映射到宿主机,只被连接的服务访问

extends

对docker-compose.yml的扩展,配置在服务中

image

使用的镜像名称或镜像 ID

links

链接到其它服务中的容器(一般桥接网络模式使用)

net

设置容器的网络模式(四种:bridge, none, container:[name or id]和host)

ports

暴露端口信息,主机和容器的端口映射

volumes

数据卷所挂载路径设置

Docker Compose 常用命令

命令

说明

docker-compose build

构建项目中的镜像,--force-rm:删除构建过程中的临时容器;--no-cache:不使用缓存构建;--pull:获取最新版本的镜像

docker-compose up -d

构建镜像、创建服务和启动项目,-d表示后台运行

docker-compose run ubuntu ls -d

指定服务上运行一个命令,-d表示后台运行

docker-compose logs

查看服务容器输出日志

docker-compose ps

列出项目中所有的容器

docker-compose pause [service_name]

暂停一个服务容器

docker-compose unpause [service_name]

恢复已暂停的一个服务容器

docker-compose restart

重启项目中的所有服务容器(也可以指定具体的服务)

docker-compose stop

停止运行项目中的所有服务容器(也可以指定具体的服务)

docker-compose start

启动已经停止项目中的所有服务容器(也可以指定具体的服务)

docker-compose rm

删除项目中的所有服务容器(也可以指定具体的服务),-f:强制删除(包含运行的)

docker-compose kill

强制停止项目中的所有服务容器(也可以指定具体的服务)

Docker-Compose工作原理

docker-compose.yml示例

#通过构建当前目录的DockerFile文件进行镜像的构建,并启动容器。

version: "3.7"

services:

  dmp-web-server:

    build: .

    ports:

      - "8077:8022"

      - "8078:8023"

Jenkins+Docker

<?xml version='1.1' encoding='UTF-8'?>
<maven2-moduleset plugin="maven-plugin@3.2">
    <actions/>
    <description></description>
    <keepDependencies>false</keepDependencies>
    <properties/>
    <scm class="hudson.scm.SubversionSCM" plugin="subversion@2.12.1">
        <locations>
            <hudson.scm.SubversionSCM_-ModuleLocation>
                <remote>https://192.168.5.124/svn/1_developmentDW/ZBMP/taskmanagement/2_src/1_trunk/task-manage</remote>
                <credentialsId>ed46a3cd-78bb-4a2f-858d-77cbe79a6099</credentialsId>
                <local>.</local>
                <depthOption>infinity</depthOption>
                <ignoreExternalsOption>true</ignoreExternalsOption>
                <cancelProcessOnExternalsFail>true</cancelProcessOnExternalsFail>
            </hudson.scm.SubversionSCM_-ModuleLocation>
        </locations>
        <excludedRegions></excludedRegions>
        <includedRegions></includedRegions>
        <excludedUsers></excludedUsers>
        <excludedRevprop></excludedRevprop>
        <excludedCommitMessages></excludedCommitMessages>
        <workspaceUpdater class="hudson.scm.subversion.UpdateUpdater"/>
        <ignoreDirPropChanges>false</ignoreDirPropChanges>
        <filterChangelog>false</filterChangelog>
        <quietOperation>true</quietOperation>
    </scm>
    <canRoam>true</canRoam>
    <disabled>false</disabled>
    <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
    <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
    <triggers>
        <hudson.triggers.TimerTrigger>
            <spec>H 23 * * *</spec>
        </hudson.triggers.TimerTrigger>
    </triggers>
    <concurrentBuild>false</concurrentBuild>
    <rootModule>
        <groupId>com.aotain</groupId>
        <artifactId>task-manage</artifactId>
    </rootModule>
    <goals>clean install -Dmaven.test.skip=true</goals>
    <aggregatorStyleBuild>true</aggregatorStyleBuild>
    <incrementalBuild>false</incrementalBuild>
    <ignoreUpstremChanges>true</ignoreUpstremChanges>
    <ignoreUnsuccessfulUpstreams>false</ignoreUnsuccessfulUpstreams>
    <archivingDisabled>false</archivingDisabled>
    <siteArchivingDisabled>false</siteArchivingDisabled>
    <fingerprintingDisabled>false</fingerprintingDisabled>
    <resolveDependencies>false</resolveDependencies>
    <processPlugins>false</processPlugins>
    <mavenValidationLevel>-1</mavenValidationLevel>
    <runHeadless>false</runHeadless>
    <disableTriggerDownstreamProjects>false</disableTriggerDownstreamProjects>
    <blockTriggerWhenBuilding>true</blockTriggerWhenBuilding>
    <settings class="jenkins.mvn.DefaultSettingsProvider"/>
    <globalSettings class="jenkins.mvn.DefaultGlobalSettingsProvider"/>
    <reporters/>
    <publishers>
        <jenkins.plugins.publish__over__ssh.BapSshPublisherPlugin plugin="publish-over-ssh@1.20.1">
            <consolePrefix>SSH:</consolePrefix>
            <delegate plugin="publish-over@0.22">
                <publishers>
                    <jenkins.plugins.publish__over__ssh.BapSshPublisher plugin="publish-over-ssh@1.20.1">
                        <configName>192.168.5.206-docker_registry</configName>
                        <verbose>false</verbose>
                        <transfers>
                            <jenkins.plugins.publish__over__ssh.BapSshTransfer>
                                <remoteDirectory>/opt/task-manage</remoteDirectory>
                                <sourceFiles>result/*.*</sourceFiles>
                                <excludes></excludes>
                                <removePrefix>result</removePrefix>
                                <remoteDirectorySDF>false</remoteDirectorySDF>
                                <flatten>false</flatten>
                                <cleanRemote>false</cleanRemote>
                                <noDefaultExcludes>false</noDefaultExcludes>
                                <makeEmptyDirs>false</makeEmptyDirs>
                                <patternSeparator>[, ]+</patternSeparator>
                                <execCommand>pwd
                                    cd /opt/task-manage
                                    chmod +x task-spark-with-dependencies.jar
                                    \cp task-spark-with-dependencies.jar task-spark/mr/
                                    rm -f task-spark.tar.gz
                                    tar zcvf task-spark.tar.gz task-spark
                                    chmod +x *.tar.gz
                                    sudo docker-compose build
                                    sudo docker-compose up -d
                                </execCommand>
                                <execTimeout>120000</execTimeout>
                                <usePty>false</usePty>
                                <useAgentForwarding>false</useAgentForwarding>
                            </jenkins.plugins.publish__over__ssh.BapSshTransfer>
                        </transfers>
                        <useWorkspaceInPromotion>false</useWorkspaceInPromotion>
                        <usePromotionTimestamp>false</usePromotionTimestamp>
                    </jenkins.plugins.publish__over__ssh.BapSshPublisher>
                </publishers>
                <continueOnError>false</continueOnError>
                <failOnError>false</failOnError>
                <alwaysPublishFromMaster>false</alwaysPublishFromMaster>
                <hostConfigurationAccess class="jenkins.plugins.publish_over_ssh.BapSshPublisherPlugin"
                                         reference="../.."/>
            </delegate>
        </jenkins.plugins.publish__over__ssh.BapSshPublisherPlugin>
    </publishers>
    <buildWrappers/>
    <prebuilders/>
    <postbuilders>
        <hudson.tasks.Shell>
            <command>pwd
                rm -rf result
                mkdir result

                \cp monitor-persist/target/*.tar.gz result/monitor-persist.tar.gz
                \cp data-collection/target/*.tar.gz result/data-collection.tar.gz
                \cp streaming-web-server/target/*.tar.gz result/streaming-web-server.tar.gz
                \cp task-web-server/target/*.tar.gz result/task-web-server.tar.gz

                #单独处理web-resource
                tar zcvf result/web-resource.tar.gz web-resource

                #单独处理task-spark
                \cp task-spark/target/*-with-dependencies.jar result/task-spark-with-dependencies.jar
            </command>
        </hudson.tasks.Shell>
    </postbuilders>
    <runPostStepsIfResult>
        <name>FAILURE</name>
        <ordinal>2</ordinal>
        <color>RED</color>
        <completeBuild>true</completeBuild>
    </runPostStepsIfResult>
</maven2-moduleset>

Jenkins中的脚本命令参考config.xml文件即可。

参考文档:

《Docker Documentation》

《使用kubernetes部署高可用Docker私有镜像仓库》

《每天5分钟玩转容器技术》

《用Docker实现私有云》

用Docker封装一个web应用(Django)

《Docker安装Tomcat》

《从零开始搭建docker私有仓库》

《从零开始使用Docker搭建Spark集群》

《Dockerfile命令详解》

《通过Dockerfile构建SpringBoot项目镜像》

《Spring Boot with Docker》

 

 

 

 

 

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值