虛擬作業環境: vagrant 和 docker

Docker 與 Vagrant 的作業環境

雖然我一直也認為作業環境、虛擬器設定是伺服器工程師才需要理會的東東,但我現在已經後悔了!要明白,開發者用的本機電腦,與真正上線服務的電腦,兩者的作業系統及相關組態,甚至連本性都很不一樣。我們需要一種方法,模擬出一個遠端的環境,既是獨立於本機的乾淨隔離環境,又要保持與本機互連的彈性。最後,為了讓整個團隊能在一致的環境下協同研發,這種組態設定知識,也要形諸文件,可程式化,可再現,予以版本控制,也就是所謂的 「infrastructure as code」!

vagrant 是甚麼?

在本質上,Vagrant並不提供虛擬化技術,本質上是一個虛擬機外掛,通過虛擬機的管理接口來管理虛擬機,讓用戶更輕鬆的進行一些常用配置,比如:CPU/Memory/IP/DISK等分配。並且提供了一些其它的管理操作:比如開機運行指定命令,鏡像二次打包,插件編寫等等。

vagrant 的設置

首先要到官網安裝
每個Box都是一個打包好的作業系統環境,當然網路上什麼都有,你不用自己去找作業系統。 (直接在Mac跑vagrant)
你可以去官方經營的 Vagrant Cloud 或網上的 vagrantbox.es 服務, Vagrant 虛擬機的 repository 中央儲存庫搜尋並安裝需要的 box。
$ vagrant box add {你想要的Box名稱} {下載網址}
你亦可以用vagrant box list這個指令看到你所擁有的所有Box,只要安裝過一次就可以重複使用。
之後,我們用vagrant init指令來產生設定檔vagrantfile來定義我們的虛擬機器。
$ cd vagrant檔案位置
$ vagrant init Box名稱
當然我們也可以不靠vagrant init指令,直接手動將以下內容寫進 Vagrantfile 檔案裡面去。
VAGRANTFILE_API_VERSION = "2"
 
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|

# 已有的box
  config.vm.box = "ubuntu/trusty64"
  
# 或者  
# 改名為 "my-redis-2"
config.vm.box = "my-redis-2"
 
# 填入該 box 檔案所在網址
config.vm.box_url = "http://bit.ly/vagrantbox-redis"

end
接著就是讓VM跑起來的指令。
$ vagrant up
看到以上這段指令跑完後,在VirtualBox那會自動定義好個虛擬機出來。而且除了原本就有的定義檔 Vagrantfile 之外,還多了一個.vagrant 目錄,Vagrant 會用它存放這個 box 執行個體的狀態及資料。
用ssh連線到機器。
$ vagrant ssh

//當然還有較硬漢式的登入方法
$ ssh  -p 2222  vagrant@127.0.0.1
現在你已經在虛擬機器上了。登入的用戶是預設的vagrant,在VM中有個/vagrant的資料夾會與你host機器的vagrant設定檔所在的資料夾共享資料。
Vagrant Command 小結
$ vagrant init  # 初始化
$ vagrant up  # 啟動虛擬機
$ vagrant halt  # 關閉虛擬機
$ vagrant reload  # 重啟虛擬機
$ vagrant status  # 虛擬機狀態
$ vagrant destroy  # 刪除此虛擬機

直接安裝環境

同時你亦可以用普通的sh檔來直接安裝環境。
cmd (vagrant@vagrant-ubuntu-quantal-64:~$是預設的用戶 )
vagrant@vagrant-ubuntu-quantal-64:~$ curl -L https://gist.github.com/gogojimmy/5523985/raw/b9d777bc380ee791c2f4534e9261b4b99289ed9f/bootstrap-chef-solo.sh | sh
bootstrap-chef-solo.sh (安裝Ruby以及一些基本的系統套件)
#!/usr/bin/env bash

# Pre-requisites
sudo apt-get -y update
sudo apt-get --no-install-recommends -y install build-essential openssl libreadline6 libreadline6-dev curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev libgdbm-dev ncurses-dev automake libtool bison subversion pkg-config libffi-dev vim

# Download and compile Ruby 2.0.0-p0
cd /tmp
wget ftp://ftp.ruby-lang.org/pub/ruby/2.0/ruby-2.0.0-p0.tar.gz
tar -xvzf ruby-2.0.0-p0.tar.gz
cd ruby-2.0.0-p0
./configure --prefix=/usr/local
make
make install

# Download and build Chef compatible with Ruby 2.0
cd /tmp
curl -o chef.tar.gz -L https://api.github.com/repos/opscode/chef/tarball/CHEF-3935
tar -xvzf chef.tar.gz
cd opscode-chef-634ad58
sudo gem build chef.gemspec
sudo gem install chef-11.4.0.gem --no-ri --no-rdoc

# The rest
sudo gem install ruby-shadow --no-ri --no-rdoc

Box打包

安裝環境之後最好習慣打包成Box,以便日後能夠重複使用。 首先是要登出VM。
vagrant@vagrant-ubuntu-quantal-64:~$ exit
vagrant package這個指令會在你目前的資料夾下建立一個vagrant.box的Box檔案,這時候我們跟剛剛一樣把它加入到我們的Box List中,以後我們就可以快速使用這個Box就好了!除此之外,可以自定Box的意義還有讓你的團隊都能用VM來擁有自己的Staging環境,例如在Rails專案中我們也可以建立一個Vagrant的設定檔來做一個給開發人員測試用的Staging環境,這時候你就可以指定好你自定的機器設定,確保每個開發人員都能擁有一樣的環境來進行開發。
$ vagrant package  
$ vagrant box add gogojimmy-ubuntu-12-10 package.box
$ vagrant list

自動設定 Guest OS 組態:Provisioning

Vagrant 提供三種自動化 provisioning 機制。由淺入深,依序是:
  1. inline script(內嵌腳本)
  2. external script file(外部腳本檔)
  3. configuration management software(組態管理軟體)

inline script(內嵌腳本)

將我們要 Vagrant 自動執行的一系列自動化 provisioning 指令,以 shell script 語法,塞入 Vagrantfile 的 config.vm.provision 設定值。
Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/trusty64"
  config.vm.provision "shell", inline: "echo Hello, World"
end
這樣執行vagrant up 時,你將看到在guest OS 啟動完畢後還會多執行了一行 echo Hello, World 指令。
$ vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Importing base box 'ubuntu/trusty64'...
[略]
==> default: Machine booted and ready!
==> default: Checking for guest additions in VM...
==> default: Mounting shared folders...
    default: /vagrant => /private/tmp/z
==> default: Running provisioner: shell...
    default: Running: inline script
==> default: stdin: is not a tty
==> default: Hello, World
因此,以Ruby的安裝為例,我們可以這樣寫。
$script = <<SCRIPT
# 安裝 Redis...
sudo apt-get install redis-server -y
# 允許 Redis bind 至全部 network interface...
sudo sed -i -e 's/^bind/#bind/' /etc/redis/redis.conf
# 重啟 Redis,讓新設定生效。
sudo service redis-server restart
SCRIPT
 
Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/trusty64"
  config.vm.provision "shell", inline: $script
end

external script file(外部腳本檔)

當然我們也可以把上述 script 內容,還可更進一步抽離到外部檔案。
在工作目錄裡,寫一個 install.sh 檔:
#!/bin/bash
 
# 安裝 Redis...
sudo apt-get install redis-server -y
# 允許 Redis bind 至全部 network interface...
sudo sed -i -e 's/^bind/#bind/' /etc/redis/redis.conf
# 重啟 Redis,讓新設定生效。
sudo service redis-server restart
然後,修改 Vagrantfile 的 config.vm.provision 設定值,讓它指涉到外部腳本檔 install.sh:
Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/trusty64"
  config.vm.provision "shell", path: "install.sh"
end

configuration management software(組態管理軟體)

比起shell script 之類,這方法將套用Chef、Puppet、Ansible、Salt的組態管理機制。
譬如說,以下這份 Ansible 檔案,可用來將 Redis 安裝在 Ubuntu 上:
---
# file: playbook.yml
# Ansible playbook for installing Redis on Ubuntu
 
- hosts: all
  sudo: True
 
  tasks:
    - name: Install the Redis package
      apt: name={{ item }} state=present update_cache=yes
      with_items: redis-server
 
    - name: let Redis bind all network interfaces, if necessary.
      lineinfile: dest=/etc/redis/redis.conf regexp="^bind 127.0.0.1" line="#bind 127.0.0.1" insertafter="^bind"
 
    - name: restart redis-server
      service: name=redis-server state=restarted enabled=yes
在這裡有兩組方法:
方法1:叫 Ansible 直接對已經 vagrant up 之後的 guest OS 進行 SSH 連線,透過此 SSH 通道設定 guest OS 組態。
$ # 先啟動 guest OS
$ vagrant up
$
$ # 將這次 Vagrant 告訴我們的 SSH host:port 填入 Ansible inventory file
$ cat <<EOF > inventory
[vagrant]
127.0.0.1 ansible_ssh_port=2222
EOF
$
$ # 叫 Ansible 透過 SSH 連線,將 "playbook.yml" 組態套用進去
$ ansible-playbook -c paramiko -u vagrant -k -vvv -i inventory playbook.yml
方法2:利用vagrantfile 的config.vm.provision來設定,接著直接 vagrant up(或 vagrant up --provision 或 vagrant provision)即可。
Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/trusty64"
 
  config.vm.provision "ansible" do |ansible|
    ansible.playbook = "playbook.yml"
    ansible.sudo = true
  end
end

vagrant 設定

再回頭處理vagrant up時生成的設定檔,vagrantfile是個有著詳細解釋的設定檔,其中這句config.vm.box = "gogojimmy-ubuntu-12-10"的意思就是告訴Vagrant要去套用哪個Box作為環境,,也就是你一開始輸入vagrant init Box名稱時所指定的Box,如果沒有輸入Box名稱的話就會是預設的base

modifyvm (VM的名稱及記憶體大小)

Virtual Box本身提供了VBoxManage這個command line tool讓你可以設定你的VM,用戶modifyvm這個指令讓你可以設定VM的名稱及記憶體大小等等。
config.vm.customize ["modifyvm", :id, "--name", "gogojimmy", "--memory", "512"]

hostname

除了Box名稱之外,設定hostname亦非常重要,有很多服務都仰賴著hostname來做為辨識,例如Puppet或是Chef,一般一些監控服務像是New Relic之類的也都是以hostname來做為辨識。
config.vm.host_name = "gogojimmy-app"

network

Vagrant有兩種橋接方式是,一種是host only,意思是說在你電腦同個區網中的其他電腦是看不到你的VM的,只有你一個人自High,另一種是Bridge,當然就是說VM會跟你區網的router去要一組IP,區網中的其他電腦也都能看到他。
一般來說因為開VM的情況都是自High居多,因此我們在設定上都是設定host only:
config.vm.network :hostonly, "192.168.33.10"
更改vagrantfile的設定後,記得要用vagrant reload的指令重開VM讓VM可以用新的設定檔跑起來。

define

另外,我們之所以會想起個虛擬台就是想要建立多個VM跑起來,並且讓他們互相溝通,有人跑Application、有人跑DB、有人跑Memcached吧。現在我們只需在設定檔中設定APP Server或DB Server的角色劃分便行。
config.vm.define :app do |app_config|
    app_config.vm.customize ["modifyvm", :id, "--name", "app", "--memory", "512"]
    app_config.vm.box = "ubuntu-12-10"
    app_config.vm.host_name = "app"
    app_config.vm.network :hostonly, "33.33.13.10"
end
config.vm.define :db do |db_config|
  db_config.vm.customize ["modifyvm", :id, "--name", "db", "--memory", "512"]
  db_config.vm.box = "ubuntu-12-10"
  db_config.vm.host_name = "db"
  db_config.vm.network :hostonly, "33.33.13.11"
end
使用了:app以及:db分別做了兩個VM的設定,並且給予不同的hostnameIP,設定好了以後再使用vagrant up將機器跑起來。用vagrant ssh appvagrant ssh db來啟動。當然VM之間的二次連接也是通做到的。
$ vagrant ssh db

vagrant@db:~$ ssh 33.33.13.10
The authenticity of host '33.33.13.10 (33.33.13.10)' can't be established.
ECDSA key fingerprint is a7:71:36:4c:01:4a:38:a2:fc:fa:ea:d7:67:63:3c:40.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '33.33.13.10' (ECDSA) to the list of known hosts.
vagrant@33.33.13.10's password:
記得設定多台機器的時候,剛剛原先在單台機器的設定必須先清除或是註解掉,如果剛剛的VM還在跑,記得要用vagrant halt先關機。

回復成乾淨狀態

如果想回復成乾淨狀態,可以輸入以下指令將你的 host OS 恢復到尚未下載啟動 guest OS 虛擬機的狀態。
#回到vagrant底下的工作目錄
$ cd demo-1 
 
#下面的指令並沒有把box 檔案本體清除,只是把藉著它而增生出來的「VirtualBox 虛擬機執行個體 (instance)」清除掉而已
$ vagrant halt ; vagrant destroy --force
$ rm -rf .vagrant Vagrantfile
 
#需要再以下面的指令強制驅離 $HOME/.vagrant.d 裡面的庫存資料
$ vagrant box remove ubuntu/trusty64
$ rm -rf $HOME/.vagrant.d/boxes/ubuntu-VAGRANTSLASH-trusty64

Vagrant 的共享管道

1. Port Forwarding

在預設的Vagrant 那早已將以下兩個 TCP port 連接起來:
  • Guest OS 的 22 port(也就是 SSH 服務預設的 TCP port)
  • Host OS 的 2222 port

2. Shared Folder

與前者類似 Vagrant 預設將以下兩個目錄連接起來:
  • Guest OS 的 /vagrant 目錄
  • Host OS 的工作目錄(即此例的 /private/tmp/demo-1)
如果你想再新增其他的共享目錄,請在 Vagrantfile 裡,找到以下這一行:
# config.vm.synced_folder "../data", "/vagrant_data"

Vagrant𥚃的Docker

一個典型的 Linux 雲端伺服主機,如果有支援所謂「Docker 化 (Dockerized)」的 server,那麼,它的軟體層,通常會由 Linux → Docker engine → Docker container → 「Docker 化的 server」層層堆疊上去。
Docker container 裡面的 port,並沒有開放給該 container 以外的『外界』存取。它已被 Docker engine 系統完美地保護起來,隔離起來。」解決方法也是一樣:port forwarding(在 Docker 世界裡,可能會用 binding、mapping、publishing 等術語來稱呼 port forwarding 這件事)。
因此,一般典型的雲端伺服主機,應該會設定成這樣:
因此,如果想在單獨一台本機電腦裡,用 Vagrant 模擬出上述的組態配置,整個軟體層次就會需要兩個層次的 port forwarding,一次是在內側的「Docker container ⇔ guest OS 之間」,一次是在外側的「guest OS ⇔ host OS 之間」:

安裝 Docker engine

用以下指令替 Ubuntu 安裝 Docker engine:
# @see https://docs.docker.com/installation/ubuntulinux/
 
export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8
export LANGUAGE=en_US.UTF-8
 
sudo apt-get update
 
# install Docker
curl -sL https://get.docker.io/ubuntu/ | sudo sh
 
# enable memory and swap accounting
sudo sed -i -e \
  's/^GRUB_CMDLINE_LINUX=.+/GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1"/' \
  /etc/default/grub
sudo update-grub
 
# enable UFW forwarding
sudo sed -i -e \
  's/^DEFAULT_FORWARD_POLICY=.+/DEFAULT_FORWARD_POLICY="ACCEPT"/' \
  /etc/default/ufw
#sudo ufw reload
 
# add 'vagrant' user to docker group
sudo usermod -aG docker vagrant

Docker的provision

方法一是我們之前學過的 inline script 安裝。
Vagrant.configure("2") do |config|
  config.vm.box = "williamyeh/ubuntu-trusty64-docker"
  config.vm.provision "shell",
    inline: "docker pull redis:latest"
end
另外一個就是Vagrant 1.6 新增的 “docker” provisioner 語法。
Vagrant.configure("2") do |config|
 
  # any base box with Docker engine installed is OK;
  # e.g, "yungsang/coreos", "yungsang/coreos-beta"
  config.vm.box = "williamyeh/ubuntu-trusty64-docker"
 
  # pull images from the Docker registry
  # see http://docs.vagrantup.com/v2/provisioning/docker.html
  config.vm.provision "docker", images: ["redis:latest"]
end

例子: Docker化的Redis Server

透過 Docker 的映像檔管理機制,下載、安裝一份「Docker 化 (Dockerized)」的 Redis server:
#官方製作的 redis 映像檔的最新版本
vagrant$  docker pull redis:latest
安裝好了之後,要先在內側的「Docker container ⇔ guest OS 」之間設定 port forwarding:
#加上一個 -p 6379:6379 參數,就可以讓 container 外面存取得到這個 container 裡面的 6379 port
vagrant$  docker run  --name my-redis  -p 6379:6379  -d  redis
另外,還要再替外側的「guest OS ⇔ host OS 之間」也做一次 port forwarding。文章中間介紹過,這需要修改 Vagrantfile 定義檔裡面的 forwarded_port 設定值。

Docker的compose (即時的啟動多個所需要的測試環境)

首先是安裝Compose。
$ sudo pip install -U docker-compose
之後,可以添加 bash 補全命令。
$ curl -L https://raw.githubusercontent.com/docker/compose/1.2.0/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose
在上面我們也曾用configuration management software(組態管理軟體)來建成環境,這次我也將使用類似的方法來進行Compose (使用YAML文件來定義容器應用的狀態)。

例一:python 的redis server

containers: 
web: 
build: . 
command: python app.py 
ports: 
- "5000:5000" 
volumes: 
- .:/code 
links: 
- redis 
environment: 
- PYTHONUNBUFFERED=1 
redis: 
image: redis:latest 
command: redis-server --appendonly yes 
上面的YAML文件定義了兩個容器應用,第一個容器運行Python應用,並通過當前目錄的Dockerfile文件構建。第二個容器是從Docker Hub註冊中心的Redis官方倉庫中構建。 links指令用來定義依賴,意思是Python應用依賴於Redis應用。
之後使用docker up啟用。

例二:haproxy-web server

在工作目錄compose-haproxy-web那分別創建兩個子目錄:haproxy 和 web。
Web 子目錄裡編寫一個 index.py 作為服務器文件:
#!/usr/bin/python
#authors: yeasy.github.com
#date: 2013-07-05

import sys
import BaseHTTPServer
from SimpleHTTPServer import SimpleHTTPRequestHandler
import socket
import fcntl
import struct
import pickle
from datetime import datetime
from collections import OrderedDict

class HandlerClass(SimpleHTTPRequestHandler):
    def get_ip_address(self,ifname):
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        return socket.inet_ntoa(fcntl.ioctl(
            s.fileno(),
            0x8915,  # SIOCGIFADDR
            struct.pack('256s', ifname[:15])
        )[20:24])
    def log_message(self, format, *args):
        if len(args) < 3 or "200" not in args[1]:
            return
        try:
            request = pickle.load(open("pickle_data.txt","r"))
        except:
            request=OrderedDict()
        time_now = datetime.now()
        ts = time_now.strftime('%Y-%m-%d %H:%M:%S')
        server = self.get_ip_address('eth0')
        host=self.address_string()
        addr_pair = (host,server)
        if addr_pair not in request:
            request[addr_pair]=[1,ts]
        else:
            num = request[addr_pair][0]+1
            del request[addr_pair]
            request[addr_pair]=[num,ts]
        file=open("index.html", "w")
        file.write("<!DOCTYPE html> <html> <body><center><h1><font color=\"blue\" face=\"Georgia, Arial\" size=8><em>HA</em></font> Webpage Visit Results</h1></center>");
        for pair in request:
            if pair[0] == host:
                guest = "LOCAL: "+pair[0]
            else:
                guest = pair[0]
            if (time_now-datetime.strptime(request[pair][1],'%Y-%m-%d %H:%M:%S')).seconds < 3:
                file.write("<p style=\"font-size:150%\" >#"+ str(request[pair][1]) +": <font color=\"red\">"+str(request[pair][0])+ "</font> requests " + "from &lt<font color=\"blue\">"+guest+"</font>&gt to WebServer &lt<font color=\"blue\">"+pair[1]+"</font>&gt</p>")
            else:
                file.write("<p style=\"font-size:150%\" >#"+ str(request[pair][1]) +": <font color=\"maroon\">"+str(request[pair][0])+ "</font> requests " + "from &lt<font color=\"navy\">"+guest+"</font>&gt to WebServer &lt<font color=\"navy\">"+pair[1]+"</font>&gt</p>")
        file.write("</body> </html>");
        file.close()
        pickle.dump(request,open("pickle_data.txt","w"))

if __name__ == '__main__':
    try:
        ServerClass  = BaseHTTPServer.HTTPServer
        Protocol     = "HTTP/1.0"
        addr = len(sys.argv) < 2 and "0.0.0.0" or sys.argv[1]
        port = len(sys.argv) < 3 and 80 or int(sys.argv[2])
        HandlerClass.protocol_version = Protocol
        httpd = ServerClass((addr, port), HandlerClass)
        sa = httpd.socket.getsockname()
        print "Serving HTTP on", sa[0], "port", sa[1], "..."
        httpd.serve_forever()
    except:
        exit()
之後生成一個臨時的 index.html 文件讓 index.py 更新。
$ touch index.html
生成一個 Dockerfile。
FROM python:2.7
WORKDIR /code
ADD . /code
EXPOSE 80
CMD python index.py
在haproxy 目錄生成一個 haproxy.cfg 文件。
global
  log 127.0.0.1 local0
  log 127.0.0.1 local1 notice

defaults
  log global
  mode http
  option httplog
  option dontlognull
  timeout connect 5000ms
  timeout client 50000ms
  timeout server 50000ms

listen stats :70
    stats enable
    stats uri /

frontend balancer
    bind 0.0.0.0:80
    mode http
    default_backend web_backends

backend web_backends
    mode http
    option forwardfor
    balance roundrobin
    server weba weba:80 check
    server webb webb:80 check
    server webc webc:80 check
    option httpchk GET /
    http-check expect status 200
在compose-haproxy-web的主目錄下編寫 docker-compose.yml 文件,這個是 Compose 使用的主模板文件。內容十分簡單,指定 3 個 web 容器,以及 1 個 haproxy 容器。
weba:
    build: ./web
    expose:
        - 80

webb:
    build: ./web
    expose:
        - 80

webc:
    build: ./web
    expose:
        - 80

haproxy:
    image: haproxy:latest
    volumes:
        - ./haproxy:/haproxy-override
        - ./haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
    links:
        - weba
        - webb
        - webc
    ports:
        - "80:80"
        - "70:70"
    expose:
        - "80"
        - "70"
之後使用sudo docker-compose up啟用。

Docker 是甚麼?

Docker 是個輕量級的作業系統的虛擬化解決方案,操作與傳統的虛擬機一樣,可最大的不同之處在於容器的實作處理方面,Docker是直接使用本地主機的作業系統層面上進行虛擬處理,而傳統的虛擬方式則是在硬體層面上進行。同時,由於Docker是直接在本地作業系統上執行的關係,其操作也相對地快而又輕巧,不花多餘的系統資源去啟動環境,大量地節約開發、測試、部署的時間。

在Mac或Win下的Docker

Docker所產生出來的Container只能是Linux,但我們常用的電腦作業系統 Mac OS及Windows下並無法直接原生使用Docker,因此必須使用Linux的VM才行。
使用VM的方法分為兩種,一種是直接自行架設VM(如VMware Workstation/Fusion、VirtualBox),再安裝Linux的VM,然後再在這個VM中安裝Docker。另一個則是使用官方的boot2docker,一次過完成上面的所有動作,並且使用類似Windows Shell指令及Mac OS的終端視窗來操作。
Docker 包括三個基本概念:
  • 映像檔(Image)
  • 容器(Container)
  • 倉庫(Repository)

Docker 映像檔

映像檔可以用來建立 Docker 容器,例如一個映像檔可以包含一個完整的 ubuntu 作業系統環境,裡面僅安裝了 Apache 或使用者需要的其它應用程式。Docker 提供了一個很簡單的機制來建立映像檔或者更新現有的映像檔,使用者甚至可以直接從其他人那裡下載一個已經做好的映像檔來直接使用。如果映像檔不存在本地端,Docker 會從映像檔倉庫下載(預設是 Docker Hub 公共註冊伺服器中的倉庫)。

Docker 容器

Docker 利用容器來執行應用,它可以被啟動、開始、停止、刪除。每個容器都是相互隔離的、保證安全的平台。另外,你亦可以把容器看做是一個簡易版的 Linux 環境(包括root使用者權限、程式空間、使用者空間和網路空間等)和在其中執行的應用程式。

Docker 倉庫

倉庫是集中存放映像檔檔案的場所,有時候會把倉庫和倉庫註冊伺服器(Registry)混為一談。而實際上,倉庫註冊伺服器上往往存放著多個倉庫,每個倉庫中又包含了多個映像檔,每個映像檔有不同的標籤(tag)。
倉庫分為公開倉庫(Public)和私有倉庫(Private)兩種形式。最大的公開倉庫是 Docker Hub,存放了數量龐大的映像檔供使用者下載。 大陸的公開倉庫包括 Docker Pool 等,可以提供大陸使用者更穩定快速的存取。當然,使用者也可以在本地網路內建立一個私有倉庫。
當使用者建立了自己的映像檔之後就可以使用 push 命令將它上傳到公有或者私有倉庫,這樣下次在另外一台機器上使用這個映像檔時候,只需要從倉庫上 pull 下來就可以了。其概念跟 Git 類似,註冊伺服器可以理解為 GitHub 這樣的託管服務。

Docker 的執行過程

1.首先當然要有安裝了Docker的Linux及硬體 (x86硬體) 
2.輸入指令即執行docker client 呼叫Docker daemon,預設使用的通訊協定是unix:///var/run/docker.sock 
3.Docker daemon透過libcontainer要求Linux核心建立Container 
4.此時Linux核心收到指示,即啟動核心的namespace建立一個獨立的空間,包括pid、network、IPC、UTS、mount等namespace,daemon根據client的參數定義來分配CPU、記憶體或磁碟IO等,Container的空殼建立完成。 
5.Damone檢查本機的現有映像檔列表,看要填入此Container空殼的映像檔之前有沒有下載過。 
6.有的話,直接從本機載入Container中,此時Container建立完成並啟動。 
7.若本機沒有此映像檔,daemon到預設的Docker Registry, 根據client的參數,下載適當的映像檔 。 
8.下載回來即將此映像檔,填入Container的空殼,此時Container即啟動完成。 

vagrant 和 docker 比較

如果你僅僅是想管理虛擬機,那麼你應該使用vagrant。如果你想快速開發和部署應用,那麼應該使用docker。 vagrant是一款管理虛擬機的工具,而docker是一款通過將應用打包到輕量級容器,而實現構建和部署的工具。
Vagrant 適合用來管理虛擬機,而docker適合用來管理應用環境。
不過這只是一般情況下的區別,他們的功能上也有很多相似的地方。但在更深層的開發過程中,docker與vagrant的使用是可以並用的。vagrant可以自動install, pull down, build, run Docker containers,比如在vagrant V1.6版本以後集成了docker-based development environments,因此Vagrant可以在windows,mac和linux上面提供docker服務。vagrant沒有想替代docker的想法,相反它還包含了docker的一些特性。
例如vagrant是可以在多個主機環境中運行,windows(XP以上),MAC(10.5以上),Linux(2.6內核版本以上),同時vagrant亦可以更好地配置網絡和文件共享(可以給一個VM配置靜態IP和端口數據轉發)。如果你使用了docker,那麼這些事情就都需要你親自動手來做了。因此vagrant 1.6中的docker-based development environments的最大優處是可以促使docker的跨平台化。

0 留言:

發佈留言