天天看點

以Chef和Ansible為例快速入門伺服器配置

這篇文章讨論了如何在我們的環境中安裝和配置軟體,這個任務通常被稱為伺服器配置(Server Provisioning)。

伺服器配置

在開始介紹現代化的工具之前,我們來看看最基本且經過實戰考驗的伺服器配置工具:shell腳本。在Chef、Ansible或Puppet出現之前,很多營運團隊使用Bash來配置伺服器(在Windows上則使用PowerShell腳本)。

例如,如果想在運作Ubuntu的Amazon EC2執行個體上安裝Nginx,可以使用以下腳本(install-nginx.sh):

#!/bin/sh
ssh -t ubuntu@$1 sudo apt-get upgrade
ssh -t ubuntu@$1 sudo apt-get -y install nginx           

複制

我們可以使用shell腳本來配置伺服器上的所有東西。據我所知,所有主流的配置工具都使用了基于安全傳輸層(如SSH)的shell指令或PowerShell(Chef可能是個例外)。即使你使用了配置工具,在某些時候也需要用到腳本。是以,當你開始使用配置工具(如Chef或Ansible)時,學習如何使用基本的shell腳本也會為你帶來很多好處。

你可能會問自己,為什麼在shell腳本已經可以完成所有工作的同時還要學習配置工具?很多環境已經使用shell腳本進行伺服器配置,那麼為什麼要使用配置工具代替它們?

首先,shell腳本通常使用的是聲明性文法。shell腳本通過運作指令序列來安裝軟體,而配置工具隻需要指定伺服器應該安裝哪些軟體,這樣就可以使用相同的代碼在不同的作業系統上、使用不同的包管理器以及指定不同的版本來安裝和配置相同的軟體。

其次,配置工具通常會提供用于組織基礎設施的方式。雖然使用shell腳本也可以做到這一點,但配置工具通常會提供更簡潔明了的方案。因為是行業标準,開發人員可以更輕松地找出QA環境中哪些伺服器運作RabbitMQ。

第三,每個主要的配置工具都有一個蓬勃發展的社群,他們建構可複用的子產品來安裝大多數開源軟體。你可以直接在子產品配置中指定記憶體限制,而不需要記住Postgres配置檔案在哪裡,這樣可以節省很多時間。

當然,原因還有很多,這裡就不一一例舉了。盡管學習曲線有點陡峭,但學習配置工具仍然是值得的。與shell腳本相比,配置工具更容易使用,便于思考,也更容易維護。

關于命名

學習使用Chef(伺服器配置工具)的前幾周給我留下了深刻的印象。入門指南展示了如何建立一個“recipe”,其中包含安裝或配置軟體的說明,我能夠了解這種比喻背後的含義。recipe必須存在于“cookbook”中,這是有道理的。然後你在“kitchen”裡測試cookbook,但我開始有點懷疑了。

這種比喻有點令人感到困惑,于是我決定去看一下其他工具,如Ansible。Ansible文檔的第一頁介紹了“playbook”的概念,而playbook包含一系列“play”。

那麼,這些問題很重要嗎?當然很重要了,因為在學習配置工具之前,你應該知道,它們很有可能會引入大量令人費解的術語。即使是為了完成基本的任務,你也必須重新學習很多術語。如果你是剛開始學習配置工具,我強烈建議你随時寫下這些術語定義,你還有很多東西要學。

每個軟體開發人員都會為現有的單詞建立不同的含義,他們甚至還會發明一些單詞,比如“uninitialize”和“unregister”。這已經成為軟體開發的一部分。

我會盡量用大家熟悉的術語來解釋這些工具。

配置管理

你決定使用花哨的配置工具在遠端伺服器上安裝Nginx。在開始設定資料庫備份節點前,一切都很順利。你已經編寫了MySQL主伺服器的配置檔案,但是你不太确定如何配置MySQL從伺服器的内部DNS位址。這個時候配置管理就派上用場了。

在設定伺服器時,最好可以将應用程式視為由兩部分組成:不可變部分(通常是代碼或編譯的二進制檔案)和可變部分(通常是配置檔案或環境變量)。大部分由社群建立的子產品預設情況下會安裝二進制檔案,并提供盡可能合理的配置,而且會為我們暴露出一些屬性,友善對其進行覆寫。

這些屬性通常包含特定于使用者環境的值。大多數配置工具都為使用者提供了一種機制,通過模闆将特定于環境的值插入到配置檔案中,或直接插入到環境變量中。

你可以使用配置工具提供的配置管理來配置MySQL主伺服器的配置檔案,然後在其中配置從伺服器。

Secret管理

這樣就可以解決上述的問題,但後來發現,你必須上傳AWS憑證才能讓MySQL從伺服器通路S3。你知道不能直接将這些憑證送出到代碼庫中,是以這些憑證隻能存在于你的機器和NSA伺服器上。

這個時候你需要的是Secret管理。

與自動化領域的所有東西一樣,你也有很多管理秘鑰的可選項。谷歌提供了一項名為KMS的服務,AWS也提供了一項名為Secret Manager的服務,Chef提供了加密資料包,Hashicorp提供了一款名為Vault的産品,Ansible也有一款名為Vault的産品。除了KMS會對字元串進行加密之外,所有這些工具都提供了相同的功能:保護對加密秘鑰的通路(這些秘鑰被用在配置管理中)。

有好幾次,我不小心将秘鑰送出到了代碼庫。這類事情一直在發生,而且非常危險。

切勿以明文形式存儲API密鑰或憑證。

可以使用Secret管了解決方案來存儲這些資料,然後将其綁定到配置工具中。

一個簡單的例子:Chef

首先需要安裝Chef Development Kit(ChefDK)。

如前所述,我們需要一個recipe來安裝Nginx。出于教學的目的,我們将從頭開始建立它,而不是從社群的cookbook中撈一個出來。

我們需要建立一個cookbook。cookbook通常存在于`cookbooks`目錄中,在項目的根目錄運作以下指令:

mkdir cookbooks           

複制

現在讓我們建立一個cookbook,用于放置我們的新recipe:

chef generate cookbook cookbooks/application           

複制

這個指令在`cookbooks/application`目錄中建立了很多檔案,我們關心的是`cookbooks/application/recipes/default.rb`這個檔案。這個檔案包含了預設的recipe,我們将安裝Nginx的指令放到這個檔案中。

apt_update

package 'nginx'

cookbook_file '/var/www/html/index.html' do
  source 'index.html'
  owner 'www-data'
  group 'www-data'
  mode '0755'
  action :create
end           

複制

這個檔案中的前兩個指令将執行你期望的操作:

  • `apt_update`更新你的aptitude包。
  • `package ‘nginx’`使用作業系統預設包管理器安裝`nginx`包(在這個示例中,它使用的是aptitude)。

最後一個指令将`cookbooks/application/files/index.html`拷貝成遠端伺服器上的`/var/www/html/index.html`,并設定檔案的權限,讓Nginx伺服器可以通路它。

這個檔案還不存在,是以需要建立它。首先要建立`檔案`目錄:

mkdir cookbooks/application/files           

複制

然後建立檔案`cookbooks/application/files/index.html`,其中包含以下内容:

<html lang="en-us">
  <head>
    <title>Hello, World!</title>
  </head>
  <body>
    Chef has landed.
  </body>
</html>           

複制

更新`packer.json`,加入Chef相關配置:

{
  "builders": [{
    "type": "amazon-ebs",
    "region": "us-east-1",
    "source_ami": "ami-04169656fea786776",
    "instance_type": "t2.small",
    "ssh_username": "ubuntu",
    "ami_name": "Ubuntu 16.04 Nginx - {{timestamp}}",
    "tags": {
      "Image": "application"
    }
  }],
  "provisioners": [{
    "type": "chef-solo",
    "cookbook_paths": ["cookbooks"],
    "run_list": ["recipe[application]"]
  }]
}           

複制

我們對之前的`packer.json`進行了兩處更改。

首先,我們為AMI添加了一個`Image`标簽。我們之前從Packer的輸出中複制AMI ID,并粘貼到Terraform代碼中。這不是一個可維護的解決方案,因為AMI ID會經常發生變化,而且我們不應該在每次發生變化時都要将更改推送到存儲庫中。相反,我們使用Terraform的`data`資源來動态讀取AMI ID(使用`Image=application`查詢最新的AMI)。

其次,我們使用`chef-solo`替換了`shell`。我們告訴它在哪裡可以找到cookbooks目錄,以及要運作哪個recipe。預設情況下,`run_list`中的`recipe[COOKBOOK]`條目将執行`recipes/default.rb`。我們也可以顯式指定explicity:`recipe [COOKBOOK::RECIPE]`來覆寫預設行為。由于我們的recipe儲存在`recipes/default.rb`中,是以将使用預設行為。

現在開始建構我們的AMI:

packer build packer.json           

複制

我們的新AMI有一個`Image`标簽,現在修改`terraform.tf`中寫死的AMI,讓它通過标簽來查找AMI。

将以下内容添加到`terraform.tf`中:

data "aws_ami" "web" {
  most_recent = true
  owners = ["self"]
  filter {                       
    name = "tag:Image"     
    values = ["application"]
  }                              
}           

複制

現在使用`aws_ami.web resource`輸出的ID替換`aws_instance.web1`和`aws_instance.web2 `resource中的AMI ID:

resource "aws_instance" "web1" {
  ami                    = "${data.aws_ami.web.id}"
  availability_zone      = "us-east-1a"
  instance_type          = "t2.small"
  vpc_security_group_ids = ["${aws_security_group.application.id}"]
  subnet_id              = "${aws_subnet.private1.id}"
}

resource "aws_instance" "web2" {
  ami                    = "${data.aws_ami.web.id}"
  availability_zone      = "us-east-1b"
  instance_type          = "t2.small"
  vpc_security_group_ids = ["${aws_security_group.application.id}"]
  subnet_id              = "${aws_subnet.private2.id}"
}           

複制

運作下面的指令建立Chef配置的伺服器,然後啟動浏覽器,打開位址為負載均衡器的域名:

terraform plan -out terraform.plan
terraform apply "terraform.plan"
open "http://$(terraform output dns)"           

複制

你應該能夠在打開的浏覽器頁面上看到:Chef has landed!

一個簡單的例子:Ansible

讓我們使用Ansible來建構這個相同的示例。首先需要安裝Ansible。

Ansible将安裝和配置說明組織到`tasks`中,然後将`tasks`組織到`playbook`中。讓我們為playbook建立一個目錄結構。

mkdir playbook
mkdir playbook/files           

複制

這并不是組織Ansible playbook的最佳實踐。因為我們的用例很簡單,是以使用了簡化版本。如果你對Ansible感興趣,應該根據官方提供的建議來建構playbook。

在`playbook/application.yml`中建立playbook,内容如下:

---
- hosts: all
  gather_facts: False
  become: yes
  pre_tasks:
  - name: Install Python 2.7
    raw: test -e /usr/bin/python || (apt -y update && apt install -y python-minimal)
- hosts: applications
  become: yes
  tasks:
  - name: Install Nginx
    apt:
      name: nginx
      state: present
      update_cache: yes
  - name: Update contents of index.html
    copy:
      src: index.html
      dest: /var/www/html/index.html
      owner: www-data
      group: www-data
      mode: 0755           

複制

這個playbook檔案包含配置我們的伺服器所需的所有資訊。現在讓我們來讨論一下它的結構。

每個playbook包含一個“play”清單,每個play包含一個“tasks”清單,task用于安裝和配置軟體。我們的playbook包含兩個play。第一個play在Ubuntu上安裝Python 2.7(用于運作Ansible)。第二個play安裝和配置Nginx。

我們在每個play的根節點配置了兩個參數:`hosts`和`become`。`hosts`參數告訴Ansible應該在哪台機器上運作playbook(“all”表示在所有機器上運作)。`become:yes`表示Ansible将通過sudo運作所有指令,否則将會出現很多權限錯誤。

play的第一個task負責安裝和配置Nginx,它将更新aptitude緩存,并確定`nginx`包存在。如果已經安裝了`nginx`包,這個指令将不執行任何操作。

第二個task将`files/index.html`拷貝到遠端伺服器上,并為其配置設定正确的權限。

這個檔案還不存在,是以讓我們建立它。将以下内容加入到`playbook/files/index.html`中:

<html lang="en-us">
  <head>
    <title>Hello, World!</title>
  </head>
  <body>
    Ansible has landed.
  </body>
</html>           

複制

這就是我們配置Ansible所需的全部内容。現在讓Packer使用這個配置。使用以下内容更新`packer.json`:

{
  "builders": [{
    "type": "amazon-ebs",
    "region": "us-east-1",
    "source_ami": "ami-04169656fea786776",
    "instance_type": "t2.small",
    "ssh_username": "ubuntu",
    "ami_name": "Ubuntu 16.04 Nginx - {{timestamp}}",
    "tags": {
      "Image": "application"
    }
  }],
  "provisioners": [{
    "type": "ansible",
    "playbook_file": "./playbook/application.yml",
    "host_alias": "applications"
  }]
}           

複制

我們隻修改了使用Ansible作為配置器,需要提供一個指向playbook檔案的路徑,我們将其設定為`./playbook/application.yml`。我們可以看到用于安裝Nginx的play頂部有一行:`hosts: applications`。這是我們用來告訴Ansible需要安裝應用程式的主機名稱。我們需要告訴Packer我們正在為其中一個主機建構映像,是以我們将`host_alias`屬性設定為`applications`。

運作下面的指令來建立Ansible配置的伺服器,然後啟動浏覽器,打開位址為負載均衡器的域名:

packer build packer.json
terraform plan -out terraform.plan
terraform apply "terraform.plan"
open "http://$(terraform output dns)"           

複制

你應該可以在打開的浏覽器頁面上看到:Ansible has landed!

英文原文:http://stephenmann.io/post/a-brief-introduction-to-provisioning/