vagrant & docker-compose

  

前言

  最近在做一个spring cloud项目,用到的组件挺多。搭建起来说不上难但是绝对不轻松,按照部署手册来一步步做也需要花费挺久。到现在已经搭建2次,一次在实体物理机上,另一次在云服务器上。就搭建了2次已经花费了一定时间,我觉得重复搭建环境不应该花费太多时间,所以这次分享一下对应这种情况的我知道的方法。

正文

docker

  说到这个话题,大家肯定会想到docker,他一个由shell层和management层两部分组成的,用来构建并运行基于lxc的虚拟Linux容器。据我所知我们有的项目也使用的docker部署。docker确实比较热门,但是今天不讲docker技术本身。大家能会想,不是已经有了docker这个容器虚拟化技术了吗?诚然使用docker部署项目真的很方便,不用考虑系统环境,只要安装docker,装载镜像,启动容器,就能将一个项目运行起来。
  不过还有一个问题需要进一步处理,比如我的项目使用了很多的 ‘外部’依赖,比如mysql,redis,es … 虽说这些依赖可以使用云服务,但是总会有自己搭建的情况存在。这时候 一个单独的docker项目镜像就无法独自运行,这个问题就出现了:docker打包了单个项目完整的运行环境,但是整个开发环境有所缺少。不过我觉得docker的伟大就在于他是一个轻量的容器,他共享主机的内核,但又做到了与主机间,容器间的隔离。
  当然,上面的问题一定要使用docker还是可以解决的,那就是下载一个原始的系统镜像比如centos,将需要的东西全都安装进去,然后保存为容器。接下来就是今天我想说的2个东西,他们同样可以解决上述问题,我个人觉得这两者可能会更好一些。

vagrant

简介

  首先使用vagrant是使用虚拟机,他不同于docker的轻量,相对于docker一两百兆的镜像,他上G的镜像有些重,适合于做一些环境上的部署工作,而docker更适合作为单个项目的部署工具。(个人觉得他们的搭配使用会更好,奈何…)
  vagrant是一个操作虚拟机的工具,一般默认使用的是virtualbox,当然也可以支持vmware,hyperV等,据说现在也已经有了docker的支持。(这边就用默认的virtualbox来说明)。
  在我没有接触vagrant的时候,使用virtualbox的时候往往会用默认的那一个管理界面,说实话很多不必要的图形界面的展示导致开虚拟机很卡

  • 默认virtualbox启动虚拟机
    vobx1 vobx2

  使用vagrant以后,迅速!流畅!不卡!有时候开发人员并不需要图形界面,而图形界面往往比较占用资源…

  • 使用vagrant启动虚拟机
    vagrant1 vagrant2

使用

安装

  vagrant的安装使用也是十分方便。在官方下载页面下载对应的版本安装。如果没有VirtualBox记得先安装(其他虚拟机也可以),安装后shell中输入 vagrant,出现以下则表示安装成功

1
2
3
4
5
$ vagrant
Usage: vagrant [options] <command> [<args>]

-v, --version Print the version and exit.
-h, --help Print this help.

下载镜像 & 缺陷

  vagrant的镜像叫做box,可以理解为docker的image。在官方网站找到自己所需的box后使用,但是这个下载速度早期因为一些不可描述的原因….

1
2
vagrant box add centos/7  #下载centos7

  • 下载速度,现在据说官方已经用上了cdn,以前这个速度真的….
    down1 down2

  所以需要自己找一些镜像网站来下载,这里是centos7的镜像 (大部分是清华和中科大的源),在下载后手动添加box

1
2
vagrant box add [给这个box起一个名字] [box的path]

  • 手动指定添加box
    addbox

初始化

  成功添加完box后 准备工作算是做好了,接下来使用init命令 将当前目录 初始化成为Vagrant环境目录,同时生成Vagrantfile文件

1
2
vagrant init [name [url]]

如果指定了第一个参数name,它将被填充到Vagranfile文件中的config.vm.box配置中。
如果指定了第二个参数url,它将被填充到Vagrantfile文件中的config.vm.box_url配置中。
可用选项:

  • -box-version:指定box的版本。

  • -f或者-force:如果指定,此命令将覆盖已存在的Vagrantfile文件。

  • -m或者-minimal:如果指定,将创建最小化的Vagrantfile文件,即比起正常Vagrantfile文件缺少指导性注释的Vagrantfile文件。

  • -output FILE:输出Vagrantfile文件到指定的文件,如果指定为“-”,表示输出到标准输出。

  • 简单的初始化

    init

Vagrantfile

   到这里先停一下,来看一下Vagrantfile里面有些什么东西

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# -*- mode: ruby -*-
# vi: set ft=ruby :

# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure("2") do |config|
# The most common configuration options are documented and commented below.
# For a complete reference, please see the online documentation at
# https://docs.vagrantup.com.

# Every Vagrant development environment requires a box. You can search for
# boxes at https://vagrantcloud.com/search.
config.vm.box = "magicCentos7"

# Disable automatic box update checking. If you disable this, then
# boxes will only be checked for updates when the user runs
# `vagrant box outdated`. This is not recommended.
# config.vm.box_check_update = false

# Create a forwarded port mapping which allows access to a specific port
# within the machine from a port on the host machine. In the example below,
# accessing "localhost:8080" will access port 80 on the guest machine.
# NOTE: This will enable public access to the opened port
# config.vm.network "forwarded_port", guest: 80, host: 8080

# Create a forwarded port mapping which allows access to a specific port
# within the machine from a port on the host machine and only allow access
# via 127.0.0.1 to disable public access
# config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1"

# Create a private network, which allows host-only access to the machine
# using a specific IP.
# config.vm.network "private_network", ip: "192.168.33.10"

# Create a public network, which generally matched to bridged network.
# Bridged networks make the machine appear as another physical device on
# your network.
# config.vm.network "public_network"

# Share an additional folder to the guest VM. The first argument is
# the path on the host to the actual folder. The second argument is
# the path on the guest to mount the folder. And the optional third
# argument is a set of non-required options.
# config.vm.synced_folder "../data", "/vagrant_data"

# Provider-specific configuration so you can fine-tune various
# backing providers for Vagrant. These expose provider-specific options.
# Example for VirtualBox:
#
# config.vm.provider "virtualbox" do |vb|
# # Display the VirtualBox GUI when booting the machine
# vb.gui = true
#
# # Customize the amount of memory on the VM:
# vb.memory = "1024"
# end
#
# View the documentation for the provider you are using for more
# information on available options.

# Enable provisioning with a shell script. Additional provisioners such as
# Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
# documentation for more information about their specific syntax and use.
# config.vm.provision "shell", inline: <<-SHELL
# apt-get update
# apt-get install -y apache2
# SHELL
end

  Vagrantfile是定义虚拟机的各种配置,主要包括三个方面的配置:虚拟机的配置、SSH配置、Vagrant的一些基础配置。大致结构如下

1
2
3
4
Vagrant.configure("2") do |config|
...
...
end
  1. config.vm.hostname = “xxx”
    配置虚拟机主机名,设置hostname非常重要,当有多台虚拟机的时候,都是依靠hostname来做区分的,比如,我安装了centos1,centos2 两台虚拟机,启动时,我可以通过vagrant up centos1来指定只启动哪一台。
  2. config.vm.synced_folder “../data”, “/vagrant_data”
    共享文件夹
  3. config.ssh.username
    默认的用户是vagrant,从官方下载的box往往使用的是这个用户名。如果是自定制的box,所使用的用户名可能会有所不同,通过这个配置设定所用的用户名。
  4. config.vm.provision
    我们可以通过这个配置在虚拟机第一次启动的时候进行一些安装配置,需要注意的是,Vagrantfile文件只会在第一次执行vagrant up时调用执行,其后如果不明确使用vagrant reload进行重新加载,否则不会被强制重新加载。
  5. config.vm.box
    box设置,该名称是再使用 vagrant init xxx 中后面跟的名字。一般来说在Vagrantfile生成的时候就已经有了
  6. config.vm.network “forwarded_port”, guest: 80, host: 8080
    配置端口转发 host(本机),guest(虚拟机), 如果要配置多个端口,需要一个一个配置
  7. config.vm.network “private_network”, ip: “192.168.33.10”
    配置私有网络,创建一个虚拟机与主机之间的私有网络,可以为虚拟机手工指定一个 IP 地址,这个 IP 地址只有主机能访问到,默认是 192.168.33.10 ,可以修改成任何的为私有网络保留的地址段里的 IP 地址,不过不能使用跟电脑内网一样的地址段,比如我的路由器上设置的内网的 IP 地址段是 192.168.1.x ,这样为这个虚拟机配置私有网络地址的时候,就不能用 192.168.1.x 这个地址段上的 IP 地址。
  8. config.vm.network “public_network”
    配置公有网络,如果想让网络里面的其它设备也可以使用虚拟机上提供的服务,用这个配置虚拟机会被分配一个内部的 IP 地址,使用这个 IP 地址,就可以访问到虚拟机上的服务了。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    WhiteMagic2014:magicCentos7 chenhaoyu$ vagrant up 
    Bringing machine 'default' up with 'virtualbox' provider...
    ==> default: Importing base box 'magicCentos7'...
    ==> default: Matching MAC address for NAT networking...
    ==> default: Setting the name of the VM: magicCentos7_default_1608628085744_17564
    ==> default: Clearing any previously set forwarded ports...
    ==> default: Clearing any previously set network interfaces...
    ==> default: Available bridged network interfaces:
    1) en6: USB 10/100 LAN
    2) en0: Wi-Fi (AirPort)
    3) p2p0
    4) awdl0
    5) en1: 雷雳 1
    6) en2: 雷雳 2
    ==> default: When choosing an interface, it is usually the one that is
    ==> default: being used to connect to the internet.
    default: Which interface should the network bridge to? 2
    ==> default: Preparing network interfaces based on configuration...
    default: Adapter 1: nat
    default: Adapter 2: bridged
    ==> default: Forwarding ports...
    default: 22 (guest) => 2222 (host) (adapter 1)
    ==> default: Booting VM...

      如果使用了public_network,在启动时会看到上面的提示 Available bridged network interfaces: 让你选择一个连网的方式(每个人看到的可能不一样),在上面我选择的是 wifi

启动 & 登录

  上面一小节简单说了一下Vagrantfile,更详细的内容可以在官网上查看文档或者百度一下,详细的不再展开。在配置完成后只需要一条命令就可以启动虚拟机了

1
$ vagrant up  #在Vagrantfile 的目录下
  • 启动中…
    up1 up2

  启动成功后 使用以下命令即可连接进入虚拟机

1
$ vagrant ssh 
  • 登录虚拟机
    ssh

  到这里 搭建一个虚拟机就已经结束了,已经可以安装所需要的软件正常使用了。

登出 & 关闭虚拟机

   登出和关闭都很简单。

1
2
$ exit #登录状态下登出,就是正常的linux登出
$ vagrant halt #在Vagrantfile的目录下 关闭虚拟机
  • 关闭虚拟机
    halt

box打包

   在虚拟机中做了一些操作,比如安装了一些开发环境所需要的软件(数据库,redis之类的),为了以后的开包即用可以将其制作为box。

  • 先使用 vboxmanage list vms 查看当前虚拟机的名字

  • 使用vagrant package –base [虚拟机名字] –output [输出的box名字] 命令打包

  • 打包操作

    package

常用命令

   这边列出一些常用的命令,更多的进阶操作可以查看官方文档或者百度

命令 操作
vagrant box list 查看目前已有的box
vagrant box add 新增加一个box
vagrant box remove 删除指定box
vagrant init 初始化配置vagrantfile
vagrant up 启动虚拟机
vagrant ssh ssh登录虚拟机
vagrant suspend 挂起虚拟机
vagrant reload 重启虚拟机
vagrant halt 关闭虚拟机
vagrant status 查看虚拟机状态
vagrant destroy 删除虚拟机

vagrant & docker

   简单介绍了一下vagrant后,大家对他有了一个了解。他和docker之间并不是 谁比谁强的关系。我觉得vagrant 和 docker的组合会更好。
   docker基于linux内核,所以在windows上就不能直接使用docker,如果这时候想使用docker,使用vagrant创建一个虚拟机会是不错的选择。将开发环境所需要的依赖安装至虚拟机中,打包成box。而docker作为项目的运行容器。 将docker运行在 虚拟机中做到真正的随带随走,拆包即用。

docker-compose

简介

   之前抛出的问题是:一个项目使用docker部署,依赖了许多外部组件。就单纯这个问题而言 docker-compose 同样能解决。compose 是一个定义和运行多docker容器的工具,通过使用yml配置文件来编排一系列docker服务,然后使用一个命令就可以创建并启动所有服务。(注意:他仍然是使用docker,所以不能直接在windows下使用)
   就微服务而言,compose还有另一个好处:之前使用docker是这样的,定义dockerfile文件,docker build,docker run等命令操作容器,docker run有时候还需要加上很多参数。而微服务往往需要部署多个服务,每个服务可能还要部署多个实例,要是所有的微服务都要手动操作,那会是比较麻烦的一件事,compose其实是解决这一问题的。而对于上面的问题,我们可以把外部“依赖”一起编排进compose的yml中,在启动项目的时候 优先启动那些“依赖”。
  打一个不恰当的比方,你打算做饭,但是没有材料。

  • 使用虚拟机是 准备好一个厨房,里面事先准备好了你所有需要的东西,然后你进去做饭。
  • 使用compose是,在你准备做饭的时候,临时帮你准备好你需要的东西,在准备好之后,你再进去做饭。

使用

安装

   macos 和 windows的docker桌面版本身就包含了compose,linux可以使用官方推荐的安装方式,当然也可以怎么简单怎么来 比如 yum,apt…

1
2
3
4
5
6
7
8
9
# 官方安装方式
# 1.27.4 版本可以替换成最新的稳定版本
sudo curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

# 安装完成
$ docker-compose --version
docker-compose version 1.27.4, build 1110ad01

准备

   compose依赖docker,需要有docker image 或者dockerfile。如果使用dockerfile compose会在运行的时候根据dockerfile制作镜像后再运行。常用镜像也可以从docker hub获取。

  • docker镜像
    dockerimage

yml

   前面说了compose使用yml配置文件来编排一系列docker服务,现在看一下这个配置该怎么写,首先文件名固定为docker-compose.yml,下面是官方给出的一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: "3.9"  # optional since v1.27.0
services:
web:
build: .
ports:
- "5000:5000"
volumes:
- .:/code
- logvolume01:/var/log
links:
- redis
redis:
image: redis
volumes:
logvolume01: {}
version

   compose配置文件格式的版本,有3个版本,分别为1, 2.x 和 3.x。目前主流的为 3.x 其支持 docker 1.13.0 及其以上的版本。详细的版本选择请参照官方对照

services

   定义所有的 service 信息, services 下面的第一级别的 key 既是一个 service 的名称,比如上面实例中的web,redis表示这一个compose下有2个service。
   先看web服务,build表示使用当前目录中的Dockerfile 来构建的镜像。ports中指定将容器和主机的5000端口绑定。
   然后volumes用2种方式定义了磁盘映射。第一种用了绝对路径把容器中的/code目录映射到了当前目录,这种方式比较直观,但是需要管理本地的路径。第二种用了卷标的方式,看最后两行,申明了一个logvolume01卷,然后在将容器的/var/log目录映射到刚刚申明的logvolume01卷中。用这种方式看起来比较简洁,但是缺点是你不知道数据存在哪里。

1
2
3
4
5
# 查看所有的volumes
$docker volume ls | grep logvolume01

# 查看volume对应的真实地址
$ docker volume inspect logvolume01
  • 查看真实地址
    findvolume
       接着links将指定容器连接到当前连接,可以设置别名,避免ip方式导致的容器重启动态改变的无法连接情况,对于web通过links redis,可以直接使用redis这个名称来访问redis服务(可以当成电脑改host)。
       再来看redis服务,使用的image表示他直接使用已有的redis镜像来构建容器,而非根据dockerfile先创建镜像
volumes

   上文已经提到过了,申明一个卷标,可以被service中的volumes模块引用。

一个实例

   稍微简单过了一下官方模板,现在来看一个实际使用的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
version: "3"                                        #版本3
services:
magicbot: #机器人业务逻辑服务
image: java:8u111 #使用java:8u111镜像
container_name: magicbot #给容器取一个别名
restart: on-failure #重启策略
environment: #运行时参数
- TZ="Asia/Shanghai"
volumes: #磁盘映射
- ./magicBot/magicBot.jar:/root/magicBot.jar
- ./botLogs:/logs
- ./magicBot:/root
- /etc/localtime:/etc/localtime
ports: #端口映射
- "28282:28282"
entrypoint: java -jar /root/magicBot.jar #启动后执行命令
networks: #使用网络bot_net
- bot_net

cqhttp: #机器人通讯服务
image: richardchien/cqhttp:latest
container_name: cqhttp
restart: on-failure
env_file: .env #加载env_file的path
environment:
- VNC_PASSWD=${VNC_PWD} #这里${VNC_PWD}是读取.env文件中的变量
- COOLQ_ACCOUNT=${QQ_ACCOUNT}
- COOLQ_URL=${COOLQ_VERSION_URL}
- CQHTTP_USE_HTTP=true
- CQHTTP_USE_WS=false
- CQHTTP_USE_WS_REVERSE=false
- CQHTTP_POST_URL=http://magicbot:28282
- CQHTTP_SERVE_DATA_FILES=false
- CQHTTP_UPDATE_SOURCE=github
- CQHTTP_UPDATE_CHANNEL=stable
- CQHTTP_CHECK_UPDATE=false
- CQHTTP_PERFORM_UPDATE=false
- CQHTTP_SHOW_LOG_CONSOLE=true
- CQHTTP_LOG_LEVEL=info
volumes:
- ./cqhttp:/home/user/coolq
ports:
- "29000:9000" #vnc
- "25700:5700" #http api
networks:
- bot_net
networks: #网络配置
bot_net: #声明一个bot_net网络使用桥接模式
driver: bridge

   上文我用的是v3版本,不同版本之间有一些细小的差别。同时也有很多具体的详细配置,太多了具体的不一一列举了,可以查看官方文档

启动

   在写完docker-compose.yml后 可以通过以下命令启动

1
2
$docker-compose up      #前台运行
$docker-compose up -d #后台运行
  • 前台运行
    start

常用命令

   除了刚刚的启动命令,下面列出一些常用的命令,更多的可以使用 docker-compose -h 查看帮助文档

命令 操作
docker-compose [-f <arg>…] up -d 部署一个 compose 应用,默认会读取当前目录下的yml配置,可以使用-f 显示指定需要使用的文件
docker-compose stop 停止一个compose应用下所有的容器
docker-compose start 启动一个停止的compose服务
docker-compose restart 重启compose服务
docker-compose down 停止并删除运行中的 compose 应用,同样的删除容器和网络,但是不会删除卷和镜像
docker-compose exec 在一个运行中的容器中执行一个命令,类似docker的exec命令,一般在yml目录下 使用 docker-compose exec [服务名] bash 进入容器内部
docker-compose rm 删除已停止的 compose 应用,会删除容器和网络,但是不会删除卷和镜像
docker-compose ps 用于列出compose 应用中的各个容器,输出内容包括当前状态、容器运行的命令以及网络端口
docker-compose logs 查看compose应用的log,带上-f就是实时查看,后面跟上某一个服务名,可以查看具体某一个服务的log。docker-compose logs -f xxx服务

   这边要注意:start,stop,restart这3个命令。是针对容器的,如果不是进入容器做修改,直接修改了yml中的一些配置,restart重启变更是不会生效的。这时候需要使用docker-compose up 你刚刚修改的服务名 重新构建。 其实使用compose启动项目以后,运行的一个个容器也可以直接使用docker的命令来操作。我觉得compose最精华的地方就在于在yml中编排了所有的容器,减少了在部署过程中的操作。

结语

   本文简单介绍了一下 vagrant 和 docker-compose。一个是便于操作虚拟机的工具,另一个是编排docker的工具。两者之间没有强弱之分,根据实际情况结合使用效果会更好。哪怕这2个都不用也没有什么影响。不过既然知道了,有机会不妨尝试一下吧!
   最后感谢大家的阅读。