原标题:技术贴 丨 Android 蓝牙BLE开发Docker入门与WMS2.0实例
文:王洋
从部署一个应用程序开始
1
物理机
在没有计算机虚拟化技术的年代,部署一个应用程序大概需要几个步骤?
第一步肯定是准备一台物理服务器,然后在服务器上安装操作系统,有了操作系统后才安装运行我们的应用程序。其中环境配置应该是软件部署中最麻烦的事情之一。环境配置主要包括操作系统和各种库,组件的安装,只有环境正确了,一个应用才能正常启动。举例来说,如果想要正常使用WMS2.0项目,Tomcat服务,数据库等等必不可少。
环境配置如此麻烦,换一台机器,就要重来一次,旷日费时。很多人想到,能不能从根本上解决问题,软件可以带环境安装?也就是说,安装的时候,把原始环境一模一样地复制过来。
远古时代的应用程序部署
2
虚拟化技术
虚拟机(virtual machine)就是带环境安装的一种解决方案。它可以在一种操作系统里面运行另一种操作系统,比如在 Windows 系统里面运行 Linux 系统。应用程序对此毫无感知,因为虚拟机看上去跟真实系统一模一样,而对于底层系统来说,虚拟机就是一个普通文件,不需要了就删掉,对其他部分毫无影响。
虽然用户可以通过虚拟机还原软件的原始环境。但是,这个方案有几个缺点。
虚拟机
资源占用多 虚拟机会独占一部分内存和硬盘空间。它运行的时候,其他程序就不能使用这些资源了。哪怕虚拟机里面的应用程序,真正使用的内存只有 1MB,虚拟机依然需要几百 MB 的内存才能运行。
冗余步骤多 虚拟机是完整的操作系统,一些系统级别的操作步骤,往往无法跳过,比如用户登录。
启动慢 启动操作系统需要多久,启动虚拟机就需要多久。可能要等几分钟,应用程序才能真正运行。
3
Linux 容器
由于虚拟机存在这些缺点,Linux 发展出了另一种虚拟化技术:Linux 容器(Linux Containers,缩写为 LXC)。
Linux 容器不是模拟一个完整的操作系统,而是对进程进行隔离。或者说,在正常进程的外面套了一个保护层。对于容器里面的进程来说,它接触到的各种资源都是虚拟的,从而实现与底层系统的隔离。
由于容器是进程级别的,相比虚拟机有很多优势。
启动快 容器里面的应用,直接就是底层系统的一个进程,而不是虚拟机内部的进程。所以,启动容器相当于启动本机的一个进程,而不是启动一个操作系统,速度就快很多。
资源占用少 容器只占用需要的资源,不占用那些没有用到的资源;虚拟机由于是完整的操作系统,不可避免要占用所有资源。另外,多个容器可以共享资源,虚拟机都是独享资源。
体积小 容器只要包含用到的组件即可,而虚拟机是整个操作系统的打包,所以容器文件比虚拟机文件要小很多。
总之,容器有点像轻量级的虚拟机,能够提供虚拟化的环境,但是成本开销小得多。
4
Docker是什么
Docker 属于 Linux 容器的一种封装,提供简单易用的容器使用接口。它是目前最流行的 Linux 容器解决方案。
Docker 将应用程序与该程序的依赖,打包在一个文件里面。运行这个文件,就会生成一个虚拟容器。程序在这个虚拟容器里运行,就好像在真实的物理机上运行一样。有了 Docker,就不用担心环境问题。
Docker 的核心理念是 Build, Ship, and Run Any App, Anywher,即一次封装,到处运行。
Docker容器与系统关系示意图
总体来说,Docker 的接口相当简单,用户可以方便地创建和使用容器,把自己的应用放入容器。容器还可以进行版本管理、复制、分享、修改,就像管理普通的代码一样。
5
Docker的实际应用场景
Docker提供了轻量级的虚拟化,它几乎没有任何额外开销。其次,相比于虚拟机,你可以在同一台机器上创建更多数量的容器。Docker的另外一个优点是容器的启动与停止都能在几秒中内完成。
基于以上这些优势,Docker在实际应用场景中可以起到怎样的作用呢?
下面是一些Docker的使用场景,它为你展示了如何借助Docker的优势,在低开销的情况下,打造一个一致性的环境。
简化配置
代码流水线(Code Pipeline)管理
提高开发效率
隔离应用
整合服务器
调试能力
多租户环境
快速部署
6
Docker的用途
Docker 的主要用途,目前有三大类。
提供一次性的环境。比如,本地测试他人的软件、持续集成的时候提供单元测试和构建的环境。
组建微服务架构。通过多个容器,一台机器可以跑多个服务,因此在本机就可以模拟出微服务架构。
提供弹性的云服务。因为 Docker 容器可以随开随关,很适合动态扩容和缩容。
Docker的安装配置
Docker 的安装教程可以自行搜索。
docker io :docker1.0版本
docker ce :最新版的docker 社区版
docker ee :最新版的docker 企业版
Docker的三大组件
1
仓库(Repository)
仓库(Repository)是集中存储镜像的地方,docker 仓库和git 仓库的概念类似。
Docker仓库
公共仓库
公共仓库一般是指Docker Hub,除了获取镜像外,我们也可以将自己构建的镜像存放到Docker Hub,这样,别人也可以使用我们构建的镜像。
私有仓库
有时候自己部门内部有一些镜像要共享时,如果直接导出镜像拿给别人又比较麻烦,使用像Docker Hub这样的公共仓库又不是很方便,这时候我们可以自己搭建属于自己的私有仓库服务,用于存储和分布我们的镜像。
2
镜像(image)
什么是Docker镜像?
简单地理解,Docker镜像就是一个Linux的文件系统(Root FileSystem),这个文件系统里面包含可以运行在Linux内核的程序以及相应的数据。
一般而言, Linux分为两个部分:Linux内核(Linux Kernel)与用户空间,而真正的Linux操作系统,是指Linux内核,我们常用的Ubuntu、CentOS等操作系统其实是不同厂商在Linux内核基础上添加自己的软件与工具集(tools)形成的发布版本(Linux Distribution)。
这里强调一下镜像的两个特征:
镜像是分层(Layer)的:即一个镜像可以多个中间层组成,多个镜像可以共享同一中间层,我们也可以通过在镜像添加多一层来生成一个新的镜像。
镜像是只读的(read-only):镜像在构建完成之后,便不可以再修改,而上面我们所说的添加一层构建新的镜像,这中间实际是通过创建一个临时的容器,在容器上增加或删除文件,从而形成新的镜像,因为容器是可以动态改变的。
通过下面的示意图,可以更好地理解:
Docker镜像与Linux的关系示意图
一些镜像的命令
Docker中与镜像操作相关的命令都在docker image这条子命令下,通过docker image --help这条命令,可以看到docker image子命令的详细文档,如下:
镜像的获取
1.从远程仓库利用命令
docker pull [OPTIONS] NAME[:TAG|@DIGEST]
可以从远处仓库拉取镜像。
要拉取镜像,需要指定Docker Registry的URL和端口号,默认是Docker Hub,另外还需要指定仓库名和标签,仓库名和标签唯一确定一个镜像,而标签是可能省略,如果省略,则默认使用latest作为标签名,而仓库名则由作者名和软件名组成。
举例来说,如果是从Docker Hub 获取一个官方Tomcat容器,可以使用一个更为简洁的命令:
docker pul tomcat:8.5.45
在获取之前可以使用命令: docker search先行搜索。但是search的结果是各个image,而不是带某个容器的版本号,建议直接去Docker Hub上搜索。
2. Dockerfile文件创建
除了直接从远程仓库获取之外,如果我们想对现有的image进行一些定制化的修改,比如定义环境变量,复制文件做初始数据,制定用户,添加描述信息等等,则需要使用Dockerfile文件创建image。
查看本地镜像
通过上面的方法我们将镜像拉取到了本地,那要如何查看本地有哪些镜像呢?通过下面的命令我们可以查看本地的全部镜像:
$ docker image ls
当然Docker提供了更简洁的写法,如下:
$ docker images
虚悬镜像
我们知道Docker镜像名由仓库名和标签组成,但有时候我们会看到仓库名和标签皆为none的镜像,我们称为这种镜像为虚悬镜像,如下图所示:
虚悬镜像一般是当我们使用docker pull拉取最新镜像时,生成的新的镜像,所以仓库名和标签给了新的镜像,旧的镜像仓库和标签则被取消,成为虚悬镜像。
我们可以使用下面的语句打印所有的虚悬镜像:
$ docker image ls -f dangling=true
一般的虚悬镜像已经没有什么作用了,所以可以清理掉的,下面的命令可以清除所有的虚悬镜像:
$ docker image prune
不过,如果我们想保留一些有用的虚拟镜像时,可以使用docker tag命令重新给镜像起个仓库名和标签:
$ docker tag 621d57f27e93 "test:1.0"
镜像导出与导入
如果想与别人共享某个镜像,除了从镜像服务仓库中pull镜像和把镜像push到仓库上去之外,其实我们还可以将本地构建好的镜像直接导出并保存为文件发送给别人,如下:
$ docker image save /tmp/test_image.tar.gz
而当你拿到别人导出的镜像文件,你可以使用docker load 命令把镜像加载到本地的Docker镜像列表中,如下:
$ docker load < /tmp/test_image.tar.gz
删除本地镜像
要删除一个或多个本地的镜像,可以使用下面的命令:
$docker image rm [option] IMAGE1,IMAGE2,...IMAGEn
也可以使用更简洁的方式,如:
$docker rmi [option] IMAGE1,IMAGE2,...IMAGEn
可以使用镜像的长ID、镜像短ID、镜像摘要以及镜像名称来删除镜像,如下:
$ docker rmi f7302e4ab3a8
一般更常用镜像的短ID,如:
$ docker rmi f7302
使用镜像的摘要也可以删除镜像,镜像的摘要可以使用下面的命令查询:
$ docker image ls --digests
当然我们想要清除本地全部镜像时,可以使用下面的命令,不过一般不建议使用。
$ docker rmi $(docker images -qa)
另外,一般如果镜像已经被使用来创建容器,使用上面的命令删除会报下面的错误,告诉我们该镜像已经被使用,不允许删除。
Error response from daemon: conflict: unable to remove repository reference "mysql:5.7" (must force) - container ccd406c07a78 is using its referenced image e1e1680ac726
对于已经被用于创建容器的镜像,删除方法有两种,一种是先把容器删除,再删除镜像,另一种则只需要在删除镜像的命令中跟一个-f参数便可,如:
$ docker rim -f f7302
3
容器(Container)
容器与镜像的关系,就如同面向编程中对象与类之间的关系。
因为容器是通过镜像来创建的,所以必须先有镜像才能创建容器,而生成的容器是一个独立于宿主机的隔离进程,如果使用默认的网络模式,则有属于容器自己的网络和命名空间。
我们前面介绍过,镜像由多个中间层(layer)组成,生成的镜像是只读的,但容器却是可读可写的,这是因为容器是在镜像上面添一层读写层(writer/read layer)来实现的,如下图所示:
操作容器的相关命令
启动容器
启动容器有几种不同的方式,最常用的方法是使用docker run命令可以通过镜像创建一个容器,如:
# /bin/bash表示运行容器后要执行的命令
$ docker run -it centos /bin/bash
docker run命令有一些比较常用的参数,比如容器是一种提供服务的守护进程,那么通常需要开放端口供外部访问,如:
$ docker run -p 80:80 nginx
也可以为容器指定一个名称,如:
$ docker run -p 80:80 --name webserver nginx
另外一种则是使用docker start命令重新启动已经停止运行的容器,如:
# container_id表示容器的id
$ docker start container_id
而对于正在运行的容器,也可以通过docker restart命令重新启动,如:
# container_id表示容器的id
$ docker restart container_id
查看本地容器列表
运行容器后,我们可以通过下面的命令查看本地所有容器:
$ docker container ls
不过docker container ls也简洁的写法:
$ docker ps
上面命令执行结果如下:
上面的命令只会显示正在运行的容器,如果要显示全部容器,包含退出执行的,可以加参数-a,如:
$ docker ps -a
有时候,我们只想查到容器的ID,可以用下面的命令:
$ docker ps -aq
执行结果:
92aa1ee81a52
f189ab2f2af4
1c15459fdc8a
106f204cb58f
停止容器
对于已经不需要的容器,可以使用docker stop命令停止其运行,如:
$ docker stop container_id1,container_id2...
批量停止容器,如:
$ docker stop $(docker ps -qa)
容器的三种运行模式
概括而言,Docker容器大体上有三种运行模式,如下:
1.运行后退出
下面语句创建的容器,在运行后会退出。
$ docker run centos echo "hellowrold"
2.常驻内存,就是守护进程的模式
如果容器中运行一个守护进程,则容器会一直处于运行状态,如:
$ docker run -d -p 80:80 nginx
3.交互式
我们也可以在运行容器时,直接与容器交互。
$ docker run -it centos /bin/bash
删除容器
$ docker container rm container_id
删除容器的命令也有简洁的写法,如下:
$ docker rm container_id
也可以像上面批量停止容器一样,我们也可以批量删除容器,如:
$ docker rm $(docker ps -qa)
进入容器
对于正在运行的容器,我们也可以通过docker exec命令再次进入容器,如:
$ docker exec -it f4f184f5ffb9 /bin/bash
需要指定容器的id或name,上面的命令我们用的是ID。
导出容器为镜像
$ docker export -o ./image.tar.gz f4f184f5ffb9
将容器导出后,我们可以另外一台有安装Docker的电脑中将文件包导入成为镜像,如:
$ docker import image.tar.gz
以上谈到得是容器的概念和一些常用的命令,关于容器,还可以设置数据卷和网络空间,如有机会以后再谈。
WMS2.0实践
暂时只是把wms2.0的项目和docker技术结合在一起,并没有过多的深入。经过分析,在没有考虑微服务的情况下,将整个wms2.0项目分离,放在4个容器里面,如下图所示:
docker与wms2.0
关于容器的选择,基于以下几点考虑:
便利程度 可选基础镜像有很多,比如说springboot项目自己可以放在一个单独容器里,vue也可以放在一个单独容器里。但是这样Docker 侵入式的增加了复杂的一层,让开发、调试、修复bug异常的的复杂。考虑到现有的jeakins自动化部署,以及后续做成一个较完善的CI/CD方案,将javaWeb项目和vue项目放在同一个容器里,是比较好的选择
可管理 这点主要是指各种log的输出,后续可以增加log文件管理的框架。比如通过将tomcat里的logs文件映射到宿主机的一个目录里,很容易通过log日志掌握项目运行情况;比如将mysql数据库文件通过同样的方式,可以完成项目部署前的数据初始化等等
可移植性 主要有两种方式,第一种是通过Dockerfile文件和docker compose工具 ,第二种是通过打包镜像或容器的方式
使用Tomcat容器
获取镜像
$ docker pull tomcat:8.5.45
在生成一个容器之前,可以先创建一个挂载点volume,也称作卷
$ docker volume create tomcat_logs
使用以下命令生成并启动一个tomcat 容器:
$ docker run --name 8003tomcat -p 8003:8080 -d -v tomcat_logs:/usr/local/tomcat/webapps/logs tomcat:8.5.45
--name="nginx-lb":为容器指定一个名称;将该容器命名为8003tomcat
-p: 指定端口映射,格式为:主机(宿主)端口:容器端口; 将8003tomcat容器的8080端口映射给宿主机的8003端口
-v: 绑定一个卷;将容器内tomcat的logs目录与创建的tomcat_logs卷绑定在一起
-d: 后台运行容器,并返回容器ID;
docker run 还有以下可选的命令:
-a stdin: 指定标准输入输出内容类型,可选 STDIN/STDOUT/STDERR 三项;
-i: 以交互模式运行容器,通常与 -t 同时使用;
-P: 随机端口映射,容器内部端口随机映射到主机的高端口
-t: 为容器重新分配一个伪输入终端,通常与 -i 同时使用;
--dns 8.8.8.8: 指定容器使用的DNS服务器,默认和宿主一致;
--dns-search example.com: 指定容器DNS搜索域名,默认和宿主一致;
-h "mars": 指定容器的hostname;
-e username="ritchie": 设置环境变量;
--env-file=[]: 从指定文件读入环境变量;
--cpuset="0-2" or --cpuset="0,1,2": 绑定容器到指定CPU运行;
-m :设置容器使用内存最大值;
--net="bridge": 指定容器的网络连接类型,支持 bridge/host/none/container: 四种类型;
--link=[]: 添加链接到另一个容器; 这种方式不推荐
--expose=[]: 开放一个端口或一组端口;
使用mysql容器
在使用mysql容器的时候,主要想达成将数据的初始化放到docker中的整个工作过程的目的。启动过程中自动导入数据及数据库用户的权限设置,并且在新创建出来的容器里自动启动MySQL服务接受外部连接。
在实现的过程中主要遇到以下几点问题:
原来是直接用的mysql5.7官方镜像,所以mysql数据的导入就不好实现,因此需要在原官方镜像的基础上重新创建镜像
数据导入与mysql权限问题,由于无法绕过mysql认证步骤,在启动mysql镜像时通过免密方式.
mysql容器启动问题.
重新设置root用户密码--未解决
为了解决上一个问题,重新创建一个inossem用户
中文乱码问题
创建Dockerfile
FROM mysql:5.7
MAINTAINER wyang
LABEL version="1.0" deion="测试mysql容器" by="wyang"
#设置免密登录
ENV MYSQL_ALLOW_EMPTY_PASSWORD yes
#修改Docker容器的字符集
ENV LANG C.UTF-8
# ENV LANGUAGE C.UTF-8 #可不配置
# ENV LC_ALL C>UTF-8 #可不配置
#VOLUME 定义匿名卷
#VOLUME ["data","/var/lib/mysql"]
#VOLUME ["cnf.d","/etc/my.cnf.d"]
#EXPOSE为构建的镜像设置监听端口,使容器在运行时监听,并不会让容器监听 host 的端口
#EXPOSE 23306
#将所需文件放到容器中
COPY setup.sh /mysql/setup.sh
COPY wms.sql /mysql/wms.sql
COPY privileges.sql /mysql/privileges.sql
#设置容器启动时执行的命令
#与 RUN 指令的区别:RUN 在构建的时候执行,并生成一个新的镜像,CMD 在容器运行的时候执行,在构建时不进行任何操作
CMD ["sh", "/mysql/setup.sh"]
setup.sh
#!/bin/bash
set -e
#查看mysql服务的状态,方便调试,这条语句可以删除
echo `service mysql status`
echo '1.启动mysql....'
#启动mysql
service mysql start
sleep 3
echo `service mysql status`
sleep 3
echo '2.设置字符编码utf8....'
echo "character-set-server=utf8" >> /etc/mysql/mysql.conf.d/mysqld.cnf
service mysql restart
echo '3.开始导入数据....'
#导入数据
mysql < /mysql/wms.sql
echo '4.导入数据完毕....'
sleep 3
echo `service mysql status`
#重新设置mysql密码
echo '5.开始修改密码....'
mysql < /mysql/privileges.sql
echo '6.修改密码完毕....'
#sleep 3
echo `service mysql status`
echo 'mysql容器启动完毕,且数据导入成功'
tail -f /dev/null
wms.sql
用户权限privileges.sql返回搜狐,查看更多
责任编辑: