第 11 章:設定伺服器環境與程式
佈建伺服器方式
- 使用客製化 AMI
- 使用 packer 服務,或是在雲端服務直接建立客製化 AMI
- 使用標準 AMI 後透過指令安裝相關程式
- file upload
- exec
- Chef, Puppet, Ansible
- user data in cloud service
簡單的網頁伺服器
一台簡單的網頁伺服器需要的工作:
- 建立一個虛擬網路
- 切出一個網段
- 開啟一個虛擬機
- 放到 SSH 公鑰
- 設定防火牆
- 安裝 nginx 做為網頁服務引擎
要安裝 nginx,的方法有很多,可以登入伺服器,下指令安裝。但是這樣不符合,IaC 的精神。
我們來試試 Terraform 的佈建器 (provisioner)
佈建器 provisioner
用佈建器來完成在伺服器上安裝 nginx 這件工作。我們會用到兩個區塊,connction
跟 provisioner
區塊,兩個都要放在 aws_instance
裡面。
首先是 connection
區塊,常用的引數 (Arguments) 有:
- type: 支援
ssh
跟winrm
- user: 建立連線所使用的使用者
- host: 伺服器的 IP
- private_key: ssh 私鑰
再來是 provisioner
區塊,有分 file
, local-exec
跟 remote-exec
三種。
provisioner example
resource "aws_key_pair" "edward-key"{
ami = "$(lookup(var.AMIS,var.AWS_REGION))"
instance_type = "t2.micro"
key_name = "$(aws_keypair.mykey.key_name)"
provisioner "file"{
source = "script.sh"
destination = "/opt/script.sh"
connection{
user = "${var.instance_username}"
private_key = "${file({var.path_to_private_key})}"
}
}
provisioner "remote-exec"{
inline = [
"chmod +x /opt/script.sh",
"/opt/script.sh arguments"
]
}
}
我們要在伺服器執行一些指令,使用的是 remote-exec
,主要的引數:
- inline: 指令清單
完整範例
把輸入變數 (Input Variables) 跟輸出值 (Output Values) 加入組態檔,並使用佈建器 (provisioner) 來安裝服務。
完成的檔案清單:
- variables.tf
- main.tf
- outputs.tf
- terraform.tfvars
variables.tf
variable "aws_region" {
type = string
description = "AWS region to launch servers."
default = "ap-northeast-1"
}
variable "cidr" {
type = string
description = "vpc cidr block"
}
variable "public_subnet" {
type = string
description = "public subnet cidr block"
}
variable "public_key_path" {
type = string
description = "Path to SSH public key"
default = "~/.ssh/id_rsa.pub"
}
variable "private_key_path" {
type = string
description = "Path to SSH private key"
default = "~/.ssh/id_rsa"
}
variable "ami" {
type = string
description = "ami id"
}
variable "my_ip" {
type = string
description = "my ip to allow ssh connection"
}
main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "3.5.0"
}
}
}
provider "aws" {
profile = "default"
region = "ap-northeast-1"
}
resource "aws_vpc" "this" {
cidr_block = var.cidr
}
resource "aws_internet_gateway" "this" {
vpc_id = aws_vpc.this.id
}
resource "aws_route" "internet_access" {
route_table_id = aws_vpc.this.main_route_table_id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.this.id
}
resource "aws_subnet" "this" {
vpc_id = aws_vpc.this.id
cidr_block = var.public_subnet
map_public_ip_on_launch = true
}
resource "aws_security_group" "ssh" {
name = "ssh"
description = "sg for ssh incoming"
vpc_id = aws_vpc.this.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = [format("%s/32", var.my_ip)]
}
}
resource "aws_security_group" "web" {
name = "web"
description = "sg for web incoming"
vpc_id = aws_vpc.this.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# outbound internet access
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_key_pair" "practice" {
key_name = "practice"
public_key = file(var.public_key_path)
}
resource "aws_instance" "web" {
ami = var.ami
instance_type = "t2.micro"
key_name = aws_key_pair.practice.id
vpc_security_group_ids = [
aws_security_group.ssh.id,
aws_security_group.web.id,
]
subnet_id = aws_subnet.this.id
connection {
type = "ssh"
user = "ubuntu"
host = self.public_ip
private_key = file(var.private_key_path)
}
provisioner "remote-exec" {
inline = [
"sudo apt-get update",
"sudo apt-get install -y nginx",
]
}
}
output.tf
output "vpc_id" {
description = "ID of VPC"
value = aws_vpc.this.id
}
output "web_instance_id" {
description = "ID of web instance"
value = aws_instance.web.id
}
output "web_public_ip" {
description = "Public IP of web server"
value = aws_instance.web.public_ip
}
terraform.tfvars
cidr = "10.0.0.0/16"
public_subnet = "10.0.1.0/24"
public_key_path = "~/.ssh/id_rsa.pub"
# Ubuntu Server 20.04 LTS (HVM), SSD Volume Type, 64-bit x86
ami = "ami-0461b11e2fad8c14a"
執行
先查詢你目前的 public ip,接著執行 apply
指令並帶入變數
terraform apply -var="my_ip=xxx.xxx.xxx.xxx"
...
Apply complete! Resources: 8 added, 0 changed, 0 destroyed.
Outputs:
vpc_id = vpc-03fa104e06a386d5d
web_instance_id = i-02726dce6cb801181
web_public_ip = 123.123.123.123
完成了就可以開啟 http://123.123.123.123
驗收一下網頁服務。
佈建器 (provisioner) 這個方法並不是 Terraform 推薦的方法,在測試的過程中我有遇到幾次的失敗。
後面我們再找其他的辦法來試試
前面我們所使用佈建器 (Provisioner) 的 remote-exec
不怎麼好用,而且 Terraform 無法檢查設定有沒有變動。
aws 有一個叫做使用者資料 (User Data) 的功能,可以輔助我們設定虛擬機。只要在建立機器時傳送使用者資料 (User Data),在執行個體 (instance) 啟動之後就會執行指令。
使用者資料 (User Data)
使用者資料是基於 cloud-init
這個工具在運作的。cloud-init 是由 Canonical 所發佈的工具
目的就是要讓雲端虛擬機初始化可以更容易的自動化。目前常見的雲端平台跟多數的 Linux 作業系統都有支援 cloud-init 功能。
使用者資料可以使用 gzip 壓縮,大小通常會有 16384 位元組的上限。支援兩種格式:
- shell 腳本: 檔案以
#!
開始 - cloud-int 組態: 檔案以
#cloud-config
開始
以下提供幾個簡單的範例,完整的設定方式請詳閱 Modules - cloud-init
建立使用者
使用 users
建立使用者,並可以設定 sudo
權限,放入公鑰等等。
#cloud-configusers:
- default
- name: barfoo
sudo: ALL=(ALL) NOPASSWD:ALL
groups: users, admin
ssh_import_id: None
lock_passwd: true
ssh_authorized_keys:
- <ssh pub key 1>
建立檔案
使用 write_files
把檔案放到目的地。
#cloud-configwrite_files:
- path: /var/www/html/terraform.html
content: |
<h1>Provisioning via Terraform</h1>
安裝套件
要更新 apt 套件資料庫的話,要把 package_update
設定為 true
。
要安裝的套件全部列在 packages
下面 。
#cloud-configpackage_update: true
packages:
- nginx
- git
Reference
我們來實際用使用者資料設定虛擬機,以下是預計要讓使用者資料處理的工作:
- 建立一個叫做
terraform
的使用者 - 安裝 nginx
- 放入一個 HTML 檔案
建立使用者資料
建立使用者資料的檔案 user_data.yaml
user_data.yaml
#cloud-config# create usersusers:
- default
- name: terraform
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
# install nginxpackage_update: true
packages:
- nginx
# put html filewrite_files:
- path: /var/www/html/terraform.html
content: |
<h1>Provisioning via Terraform</h1>
設定組態檔
使用 template_file
這個 data
區塊來載入 user_data.yaml
,再把載入的資料放到 aws_instance
的 user_data
引數中。
data
template_file
的引數:
- template: 放模版資料,可以用
file()
函數來載入檔案。
上面的設定都放在 main.tf
裡面,完整檔案可以參考: https://github.com/nyogjtrc/practice-terraform/tree/master/web-server-user-data
main.tf
data "template_file" "user_data" {
template = file("user_data.yaml")
}
resource "aws_instance" "web" {
ami = var.ami
instance_type = "t2.micro"
key_name = aws_key_pair.practice.id
vpc_security_group_ids = [
aws_security_group.ssh.id,
aws_security_group.web.id,
]
subnet_id = aws_subnet.this.id
user_data = data.template_file.user_data.rendered
tags = {
Name = "Web-Terraform"
topic = "web-server-user-data"
}
}
執行 terraform
terraform apply
...
Apply complete! Resources: 8 added, 0 changed, 0 destroyed.
Outputs:
vpc_id = vpc-0db96967262b14fcf
web_instance_id = i-008d3a7371fba33b2
web_public_ip = 18.177.119.161
使用者資料是在虛擬機建立之後才執行的,所以需要稍微等一下
aws 自動分配了 18.177.119.161 給虛擬機,在瀏覽器開啟 http://18.177.119.161/terraform.html 就可以看到我們剛剛放上去的檔案