ไม่นานมานี้ ฉันจำเป็นต้องเขียน Playbooks Ansible หลายเล่มเพื่อเตรียมเซิร์ฟเวอร์สำหรับการปรับใช้แอปพลิเคชัน Rails และน่าประหลาดใจที่ฉันไม่พบคู่มือง่ายๆ ทีละขั้นตอนเลย ฉันไม่ต้องการคัดลอก Playbook ของคนอื่นโดยไม่เข้าใจสิ่งที่เกิดขึ้น และท้ายที่สุด ฉันต้องอ่านเอกสารและรวบรวมทุกอย่างด้วยตัวเอง บางทีฉันสามารถช่วยบางคนเร่งกระบวนการนี้ให้เร็วขึ้นได้ด้วยความช่วยเหลือของบทความนี้
สิ่งแรกที่ต้องทำความเข้าใจก็คือ ansible มอบอินเทอร์เฟซที่สะดวกสบายให้กับคุณเพื่อดำเนินการรายการการกระทำที่กำหนดไว้ล่วงหน้าบนเซิร์ฟเวอร์ระยะไกลผ่าน SSH ไม่มีเวทย์มนตร์ที่นี่ คุณไม่สามารถติดตั้งปลั๊กอินและทำให้แอปพลิเคชันของคุณต้องหยุดทำงานเป็นศูนย์ด้วยนักเทียบท่า การตรวจสอบ และสารพัดอื่น ๆ ทันที เพื่อที่จะเขียน Playbook คุณต้องรู้ว่าคุณต้องการทำอะไรและต้องทำอย่างไร นั่นเป็นเหตุผลที่ฉันไม่พอใจกับ Playbook สำเร็จรูปจาก GitHub หรือบทความเช่น: “คัดลอกแล้วเรียกใช้ มันจะได้ผล”
เราต้องการอะไร
อย่างที่ผมบอกไปแล้ว เพื่อที่จะเขียน Playbook คุณต้องรู้ว่าคุณต้องการทำอะไรและต้องทำอย่างไร มาตัดสินใจว่าเราต้องการอะไร สำหรับแอปพลิเคชัน Rails เราจำเป็นต้องมีแพ็คเกจระบบหลายอย่าง: nginx, postgresql (redis ฯลฯ) นอกจากนี้ เราต้องการทับทิมเวอร์ชันเฉพาะ วิธีที่ดีที่สุดคือติดตั้งผ่าน rbenv (rvm, asdf...) การเรียกใช้ทั้งหมดนี้ในฐานะผู้ใช้รูทนั้นเป็นความคิดที่ไม่ดีเสมอไป ดังนั้นคุณต้องสร้างผู้ใช้แยกต่างหากและกำหนดค่าสิทธิ์ของเขา หลังจากนี้ คุณจะต้องอัปโหลดโค้ดของเราไปยังเซิร์ฟเวอร์ คัดลอกการกำหนดค่าสำหรับ nginx, postgres ฯลฯ และเริ่มบริการเหล่านี้ทั้งหมด
เป็นผลให้ลำดับของการกระทำมีดังนี้:
- เข้าสู่ระบบในฐานะรูท
- ติดตั้งแพ็คเกจระบบ
- สร้างผู้ใช้ใหม่ กำหนดค่าสิทธิ์ คีย์ ssh
- กำหนดค่าแพ็คเกจระบบ (nginx ฯลฯ ) และเรียกใช้
- เราสร้างผู้ใช้ในฐานข้อมูล (คุณสามารถสร้างฐานข้อมูลได้ทันที)
- เข้าสู่ระบบในฐานะผู้ใช้ใหม่
- ติดตั้ง rbenv และ ruby
- การติดตั้งบันเดิล
- กำลังอัพโหลดรหัสแอปพลิเคชัน
- เปิดตัวเซิร์ฟเวอร์ Puma
ยิ่งไปกว่านั้น ขั้นตอนสุดท้ายสามารถทำได้โดยใช้ capistrano อย่างน้อยก็สามารถคัดลอกโค้ดลงในไดเร็กทอรี release สลับรีลีสด้วย symlink เมื่อปรับใช้สำเร็จ คัดลอกการกำหนดค่าจากไดเร็กทอรีที่แชร์ รีสตาร์ท puma ฯลฯ ทั้งหมดนี้สามารถทำได้โดยใช้ Ansible แต่ทำไม?
โครงสร้างไฟล์
Ansible มีความเข้มงวด
คู่มือการเล่นที่เรียบง่าย
Playbook เป็นไฟล์ yml ที่ใช้ไวยากรณ์พิเศษเพื่ออธิบายว่า Ansible ควรทำอย่างไรและอย่างไร มาสร้าง Playbook เล่มแรกที่ไม่ทำอะไรเลย:
---
- name: Simple playbook
hosts: all
ที่นี่เราเพียงแค่บอกว่า playbook ของเราเรียกว่า Simple Playbook
และควรดำเนินการเนื้อหาสำหรับโฮสต์ทั้งหมด เราสามารถบันทึกไว้ในไดเร็กทอรี /ansible ด้วยชื่อได้ playbook.yml
และลองเรียกใช้:
ansible-playbook ./playbook.yml
PLAY [Simple Playbook] ************************************************************************************************************************************
skipping: no hosts matched
Ansible บอกว่าไม่รู้จักโฮสต์ที่ตรงกับรายการทั้งหมด พวกเขาจะต้องอยู่ในรายการพิเศษ
มาสร้างมันในไดเร็กทอรี ansible เดียวกัน:
123.123.123.123
นี่คือวิธีที่เราระบุโฮสต์ (ในอุดมคติแล้วโฮสต์ของ VPS ของเราสำหรับการทดสอบ หรือคุณสามารถลงทะเบียน localhost) และบันทึกไว้ในชื่อ inventory
.
คุณสามารถลองใช้ ansible ด้วยไฟล์ invetory:
ansible-playbook ./playbook.yml -i inventory
PLAY [Simple Playbook] ************************************************************************************************************************************
TASK [Gathering Facts] ************************************************************************************************************************************
PLAY RECAP ************************************************************************************************************************************
หากคุณมีสิทธิ์เข้าถึง ssh ไปยังโฮสต์ที่ระบุ ansible จะเชื่อมต่อและรวบรวมข้อมูลเกี่ยวกับระบบระยะไกล (ค่าเริ่มต้น TASK [การรวบรวมข้อเท็จจริง]) หลังจากนั้นจะให้รายงานสั้นๆ เกี่ยวกับการดำเนินการ (PLAY RECAP)
ตามค่าเริ่มต้น การเชื่อมต่อจะใช้ชื่อผู้ใช้ที่คุณใช้เข้าสู่ระบบ คงจะไม่ได้อยู่บนโฮสต์ ในไฟล์ Playbook คุณสามารถระบุผู้ใช้ที่จะใช้เชื่อมต่อโดยใช้คำสั่ง remote_user นอกจากนี้ ข้อมูลเกี่ยวกับระบบระยะไกลมักไม่จำเป็นสำหรับคุณ และคุณไม่ควรเสียเวลาในการรวบรวมข้อมูล งานนี้ยังสามารถปิดใช้งานได้:
---
- name: Simple playbook
hosts: all
remote_user: root
become: true
gather_facts: no
ลองเรียกใช้ Playbook อีกครั้ง และตรวจสอบให้แน่ใจว่าการเชื่อมต่อใช้งานได้ (หากคุณระบุผู้ใช้รูท คุณจะต้องระบุคำสั่งกลายเป็น: จริงด้วยเพื่อรับสิทธิ์ระดับสูง ตามที่เขียนไว้ในเอกสารประกอบ: become set to ‘true’/’yes’ to activate privilege escalation.
แม้ว่าจะไม่ชัดเจนนักว่าทำไมก็ตาม)
บางทีคุณอาจได้รับข้อผิดพลาดที่เกิดจากการที่ ansible ไม่สามารถระบุล่าม Python ได้ จากนั้นคุณสามารถระบุด้วยตนเองได้:
ansible_python_interpreter: /usr/bin/python3
คุณสามารถค้นหาตำแหน่งที่คุณมีหลามได้ด้วยคำสั่ง whereis python
.
การติดตั้งแพ็คเกจระบบ
การแจกจ่ายแบบมาตรฐานของ Ansible มีโมดูลมากมายสำหรับการทำงานกับแพ็คเกจระบบต่างๆ ดังนั้นเราจึงไม่จำเป็นต้องเขียนสคริปต์ทุบตีไม่ว่าด้วยเหตุผลใดก็ตาม ตอนนี้เราต้องการหนึ่งในโมดูลเหล่านี้เพื่ออัพเดตระบบและติดตั้งแพ็คเกจระบบ ฉันมี Ubuntu Linux บน VPS ของฉัน ดังนั้นเพื่อติดตั้งแพ็คเกจที่ฉันใช้ apt-get
и
มาเสริม Playbook ของเราด้วยภารกิจแรก:
---
- name: Simple playbook
hosts: all
remote_user: root
become: true
gather_facts: no
tasks:
- name: Update system
apt: update_cache=yes
- name: Install system dependencies
apt:
name: git,nginx,redis,postgresql,postgresql-contrib
state: present
งานคืองานที่ Ansible จะดำเนินการบนเซิร์ฟเวอร์ระยะไกล เราตั้งชื่องานเพื่อให้เราสามารถติดตามการดำเนินการในบันทึกได้ และเราอธิบายโดยใช้ไวยากรณ์ของโมดูลเฉพาะถึงสิ่งที่ต้องทำ ในกรณีนี้ apt: update_cache=yes
- บอกว่าจะอัพเดตแพ็คเกจระบบโดยใช้โมดูล apt คำสั่งที่สองซับซ้อนกว่าเล็กน้อย เราส่งรายการแพ็คเกจไปยังโมดูล apt และแจ้งว่าเป็นเช่นนั้น state
ควรจะเป็น present
นั่นคือเราบอกว่าติดตั้งแพ็คเกจเหล่านี้ ในทำนองเดียวกัน เราสามารถบอกให้พวกเขาลบหรืออัปเดตโดยเพียงแค่เปลี่ยน state
. โปรดทราบว่าเพื่อให้ Rails ทำงานกับ postgresql ได้ เราจำเป็นต้องมีแพ็คเกจ postgresql-contrib ซึ่งเรากำลังติดตั้งอยู่ คุณจำเป็นต้องรู้และทำสิ่งนี้อีกครั้ง โดยที่ ansible ด้วยตัวเองจะไม่ทำเช่นนี้
ลองเรียกใช้ Playbook อีกครั้งและตรวจสอบว่าได้ติดตั้งแพ็คเกจแล้ว
การสร้างผู้ใช้ใหม่
ในการทำงานกับผู้ใช้ Ansible ยังมีโมดูล - ผู้ใช้ มาเพิ่มงานอีกหนึ่งงาน (ฉันซ่อนส่วนที่ทราบอยู่แล้วของ Playbook ไว้ด้านหลังความคิดเห็นเพื่อไม่ให้คัดลอกทั้งหมดทุกครั้ง):
---
- name: Simple playbook
# ...
tasks:
# ...
- name: Add a new user
user:
name: my_user
shell: /bin/bash
password: "{{ 123qweasd | password_hash('sha512') }}"
เราสร้างผู้ใช้ใหม่ ตั้งค่าสเชลล์และรหัสผ่าน แล้วเราก็ประสบปัญหาหลายประการ จะเกิดอะไรขึ้นหากชื่อผู้ใช้จำเป็นต้องแตกต่างกันสำหรับโฮสต์ที่ต่างกัน? และการเก็บรหัสผ่านเป็นข้อความที่ชัดเจนใน playbook ถือเป็นความคิดที่แย่มาก ขั้นแรกให้ใส่ชื่อผู้ใช้และรหัสผ่านลงในตัวแปรและในตอนท้ายของบทความฉันจะแสดงวิธีเข้ารหัสรหัสผ่าน
---
- name: Simple playbook
# ...
tasks:
# ...
- name: Add a new user
user:
name: "{{ user }}"
shell: /bin/bash
password: "{{ user_password | password_hash('sha512') }}"
ตัวแปรได้รับการตั้งค่าใน Playbook โดยใช้เครื่องหมายปีกกาคู่
เราจะระบุค่าของตัวแปรในไฟล์สินค้าคงคลัง:
123.123.123.123
[all:vars]
user=my_user
user_password=123qweasd
โปรดทราบคำสั่ง [all:vars]
- มันบอกว่าบล็อกข้อความถัดไปคือตัวแปร (vars) และใช้ได้กับโฮสต์ทั้งหมด (ทั้งหมด)
การออกแบบก็น่าสนใจเช่นกัน "{{ user_password | password_hash('sha512') }}"
. ประเด็นก็คือ ansible ไม่ได้ติดตั้งผู้ใช้ผ่าน user_add
เช่นเดียวกับที่คุณทำด้วยตนเอง และจะบันทึกข้อมูลทั้งหมดโดยตรง ซึ่งเป็นสาเหตุที่เราต้องแปลงรหัสผ่านเป็นแฮชล่วงหน้าด้วย ซึ่งเป็นสิ่งที่คำสั่งนี้ทำ
มาเพิ่มผู้ใช้ของเราในกลุ่ม sudo อย่างไรก็ตาม ก่อนหน้านี้เราต้องตรวจสอบให้แน่ใจว่ามีกลุ่มดังกล่าวอยู่ เพราะจะไม่มีใครทำสิ่งนี้ให้เรา:
---
- name: Simple playbook
# ...
tasks:
# ...
- name: Ensure a 'sudo' group
group:
name: sudo
state: present
- name: Add a new user
user:
name: "{{ user }}"
shell: /bin/bash
password: "{{ user_password | password_hash('sha512') }}"
groups: "sudo"
ทุกอย่างค่อนข้างง่าย เรายังมีโมดูลกลุ่มสำหรับการสร้างกลุ่มด้วยซึ่งมีไวยากรณ์คล้ายกับ apt มาก จากนั้นการลงทะเบียนกลุ่มนี้ให้กับผู้ใช้ก็เพียงพอแล้ว (groups: "sudo"
).
นอกจากนี้ การเพิ่มคีย์ ssh ให้กับผู้ใช้รายนี้ยังมีประโยชน์อีกด้วย เพื่อให้เราสามารถเข้าสู่ระบบโดยใช้คีย์ดังกล่าวได้โดยไม่ต้องใช้รหัสผ่าน:
---
- name: Simple playbook
# ...
tasks:
# ...
- name: Ensure a 'sudo' group
group:
name: sudo
state: present
- name: Add a new user
user:
name: "{{ user }}"
shell: /bin/bash
password: "{{ user_password | password_hash('sha512') }}"
groups: "sudo"
- name: Deploy SSH Key
authorized_key:
user: "{{ user }}"
key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
state: present
ในกรณีนี้การออกแบบก็น่าสนใจ "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
— คัดลอกเนื้อหาของไฟล์ id_rsa.pub (ชื่อของคุณอาจแตกต่างกัน) นั่นคือส่วนสาธารณะของคีย์ ssh และอัปโหลดไปยังรายการคีย์ที่ได้รับอนุญาตสำหรับผู้ใช้บนเซิร์ฟเวอร์
บทบาท
งานทั้งสามสำหรับการสร้างการใช้งานสามารถแบ่งได้เป็นงานกลุ่มเดียวได้อย่างง่ายดาย และจะเป็นความคิดที่ดีที่จะแยกกลุ่มนี้ออกจาก Playbook หลัก เพื่อไม่ให้ใหญ่เกินไป เพื่อจุดประสงค์นี้ Ansible จึงมี
ตามโครงสร้างไฟล์ที่ระบุไว้ในตอนต้น บทบาทจะต้องอยู่ในไดเร็กทอรีบทบาทแยกต่างหาก สำหรับแต่ละบทบาทจะมีไดเร็กทอรีแยกต่างหากที่มีชื่อเดียวกัน ภายในไดเร็กทอรีงาน ไฟล์ เทมเพลต ฯลฯ
มาสร้างโครงสร้างไฟล์กัน: ./ansible/roles/user/tasks/main.yml
(main คือไฟล์หลักที่จะถูกโหลดและดำเนินการเมื่อบทบาทเชื่อมต่อกับ playbook ไฟล์บทบาทอื่นสามารถเชื่อมต่อได้) ตอนนี้คุณสามารถถ่ายโอนงานทั้งหมดที่เกี่ยวข้องกับผู้ใช้ไปยังไฟล์นี้:
# Create user and add him to groups
- name: Ensure a 'sudo' group
group:
name: sudo
state: present
- name: Add a new user
user:
name: "{{ user }}"
shell: /bin/bash
password: "{{ user_password | password_hash('sha512') }}"
groups: "sudo"
- name: Deploy SSH Key
authorized_key:
user: "{{ user }}"
key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
state: present
ใน Playbook หลัก คุณต้องระบุเพื่อใช้บทบาทของผู้ใช้:
---
- name: Simple playbook
hosts: all
remote_user: root
gather_facts: no
tasks:
- name: Update system
apt: update_cache=yes
- name: Install system dependencies
apt:
name: git,nginx,redis,postgresql,postgresql-contrib
state: present
roles:
- user
นอกจากนี้ การอัปเดตระบบก่อนงานอื่นทั้งหมดอาจเป็นเรื่องสมเหตุสมผล โดยคุณสามารถเปลี่ยนชื่อบล็อกได้ tasks
ซึ่งพวกมันถูกกำหนดไว้ในนั้น pre_tasks
.
การตั้งค่า nginx
เราควรติดตั้ง Nginx ไว้แล้ว เราจำเป็นต้องกำหนดค่าและเรียกใช้ มาทำทันทีในบทบาท มาสร้างโครงสร้างไฟล์กัน:
- ansible
- roles
- nginx
- files
- tasks
- main.yml
- templates
ตอนนี้เราต้องการไฟล์และเทมเพลต ความแตกต่างระหว่างพวกเขาคือสามารถคัดลอกไฟล์ได้โดยตรงตามที่เป็นอยู่ และเทมเพลตจะต้องมีส่วนขยาย j2 และสามารถใช้ค่าตัวแปรได้โดยใช้เครื่องหมายปีกกาคู่เดียวกัน
มาเปิดใช้งาน nginx กันเถอะ main.yml
ไฟล์. สำหรับสิ่งนี้ เรามีโมดูล systemd:
# Copy nginx configs and start it
- name: enable service nginx and start
systemd:
name: nginx
state: started
enabled: yes
ที่นี่เราไม่เพียงแต่บอกว่าต้องเริ่มต้น nginx (นั่นคือเราเปิดตัว) แต่เราบอกทันทีว่าต้องเปิดใช้งาน
ตอนนี้เรามาคัดลอกไฟล์การกำหนดค่า:
# Copy nginx configs and start it
- name: enable service nginx and start
systemd:
name: nginx
state: started
enabled: yes
- name: Copy the nginx.conf
copy:
src: nginx.conf
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
backup: yes
- name: Copy template my_app.conf
template:
src: my_app_conf.j2
dest: /etc/nginx/sites-available/my_app.conf
owner: root
group: root
mode: '0644'
เราสร้างไฟล์การกำหนดค่า nginx หลัก (คุณสามารถรับโดยตรงจากเซิร์ฟเวอร์หรือเขียนเอง) และไฟล์การกำหนดค่าสำหรับแอปพลิเคชันของเราในไดเร็กทอรี sites_available (ไม่จำเป็น แต่มีประโยชน์) ในกรณีแรก เราใช้โมดูลการคัดลอกเพื่อคัดลอกไฟล์ (ไฟล์จะต้องอยู่ในนั้น) /ansible/roles/nginx/files/nginx.conf
). ประการที่สอง คัดลอกเทมเพลตโดยแทนที่ค่าของตัวแปร แม่แบบควรจะอยู่ใน /ansible/roles/nginx/templates/my_app.j2
). และอาจมีลักษณะดังนี้:
upstream {{ app_name }} {
server unix:{{ app_path }}/shared/tmp/sockets/puma.sock;
}
server {
listen 80;
server_name {{ server_name }} {{ inventory_hostname }};
root {{ app_path }}/current/public;
try_files $uri/index.html $uri.html $uri @{{ app_name }};
....
}
ให้ความสนใจกับส่วนแทรก {{ app_name }}
, {{ app_path }}
, {{ server_name }}
, {{ inventory_hostname }}
— นี่คือตัวแปรทั้งหมดที่มีค่า Ansible จะแทนที่ลงในเทมเพลตก่อนทำการคัดลอก สิ่งนี้มีประโยชน์หากคุณใช้ Playbook สำหรับโฮสต์กลุ่มต่างๆ ตัวอย่างเช่น เราสามารถเพิ่มไฟล์สินค้าคงคลังของเรา:
[production]
123.123.123.123
[staging]
231.231.231.231
[all:vars]
user=my_user
user_password=123qweasd
[production:vars]
server_name=production
app_path=/home/www/my_app
app_name=my_app
[staging:vars]
server_name=staging
app_path=/home/www/my_stage
app_name=my_stage_app
หากเราเปิดตัว Playbook ของเราตอนนี้ ก็จะดำเนินการตามที่ระบุไว้สำหรับโฮสต์ทั้งสอง แต่ในขณะเดียวกัน สำหรับโฮสต์ชั่วคราว ตัวแปรจะแตกต่างจากตัวแปรที่ใช้งานจริง และไม่เพียงแต่ในบทบาทและคู่มือการเล่นเท่านั้น แต่ยังรวมถึงการกำหนดค่า nginx ด้วย {{ inventory_hostname }}
ไม่จำเป็นต้องระบุในไฟล์สินค้าคงคลัง - นี่
หากคุณต้องการมีไฟล์สินค้าคงคลังสำหรับหลายโฮสต์ แต่เรียกใช้สำหรับกลุ่มเดียวเท่านั้น สามารถทำได้โดยใช้คำสั่งต่อไปนี้:
ansible-playbook -i inventory ./playbook.yml -l "staging"
อีกทางเลือกหนึ่งคือการมีไฟล์สินค้าคงคลังแยกกันสำหรับกลุ่มต่างๆ หรือคุณสามารถรวมทั้งสองวิธีเข้าด้วยกันหากคุณมีโฮสต์ที่แตกต่างกันจำนวนมาก
กลับไปตั้งค่า nginx กันดีกว่า หลังจากคัดลอกไฟล์การกำหนดค่าแล้ว เราจำเป็นต้องสร้าง symlink ใน sitest_enabled ถึง my_app.conf จาก sites_available และรีสตาร์ท nginx
... # old code in mail.yml
- name: Create symlink to sites-enabled
file:
src: /etc/nginx/sites-available/my_app.conf
dest: /etc/nginx/sites-enabled/my_app.conf
state: link
- name: restart nginx
service:
name: nginx
state: restarted
ทุกอย่างเรียบง่ายที่นี่ - โมดูลที่สามารถเข้าใจได้อีกครั้งพร้อมไวยากรณ์มาตรฐานที่ค่อนข้างดี แต่มีจุดหนึ่ง ไม่มีประโยชน์ที่จะรีสตาร์ท nginx ทุกครั้ง คุณสังเกตไหมว่าเราไม่ได้เขียนคำสั่งเช่น: "ทำเช่นนี้" ไวยากรณ์ดูเหมือน "สิ่งนี้ควรมีสถานะนี้" มากกว่า และบ่อยครั้งนี่คือวิธีการทำงานของคำตอบที่ถูกต้อง หากมีกลุ่มอยู่แล้ว หรือมีการติดตั้งแพ็กเกจระบบแล้ว ansible จะตรวจสอบสิ่งนี้และข้ามงานไป นอกจากนี้ ไฟล์จะไม่ถูกคัดลอกหากตรงกับไฟล์ที่มีอยู่แล้วบนเซิร์ฟเวอร์โดยสมบูรณ์ เราสามารถใช้ประโยชน์จากสิ่งนี้และรีสตาร์ท nginx ได้ก็ต่อเมื่อไฟล์การกำหนดค่ามีการเปลี่ยนแปลง มีคำสั่งการลงทะเบียนสำหรับสิ่งนี้:
# Copy nginx configs and start it
- name: enable service nginx and start
systemd:
name: nginx
state: started
enabled: yes
- name: Copy the nginx.conf
copy:
src: nginx.conf
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
backup: yes
register: restart_nginx
- name: Copy template my_app.conf
template:
src: my_app_conf.j2
dest: /etc/nginx/sites-available/my_app.conf
owner: root
group: root
mode: '0644'
register: restart_nginx
- name: Create symlink to sites-enabled
file:
src: /etc/nginx/sites-available/my_app.conf
dest: /etc/nginx/sites-enabled/my_app.conf
state: link
- name: restart nginx
service:
name: nginx
state: restarted
when: restart_nginx.changed
หากไฟล์การกำหนดค่าไฟล์ใดไฟล์หนึ่งมีการเปลี่ยนแปลง จะมีการทำสำเนาและตัวแปรจะถูกลงทะเบียน restart_nginx
. และเฉพาะในกรณีที่ตัวแปรนี้ได้รับการลงทะเบียนแล้วเท่านั้น บริการจึงจะเริ่มต้นใหม่ได้
และแน่นอน คุณต้องเพิ่มบทบาท nginx ให้กับ Playbook หลัก
การตั้งค่า postgresql
เราจำเป็นต้องเปิดใช้งาน postgresql โดยใช้ systemd ในลักษณะเดียวกับที่เราทำกับ nginx และยังสร้างผู้ใช้ที่เราจะใช้เพื่อเข้าถึงฐานข้อมูลและฐานข้อมูลด้วย
มาสร้างบทบาทกันเถอะ /ansible/roles/postgresql/tasks/main.yml
:
# Create user in postgresql
- name: enable postgresql and start
systemd:
name: postgresql
state: started
enabled: yes
- name: Create database user
become_user: postgres
postgresql_user:
name: "{{ db_user }}"
password: "{{ db_password }}"
role_attr_flags: SUPERUSER
- name: Create database
become_user: postgres
postgresql_db:
name: "{{ db_name }}"
encoding: UTF-8
owner: "{{ db_user }}"
ฉันจะไม่อธิบายวิธีเพิ่มตัวแปรลงในสินค้าคงคลัง ซึ่งได้ทำไปแล้วหลายครั้ง รวมถึงไวยากรณ์ของโมดูล postgresql_db และ postgresql_user ข้อมูลเพิ่มเติมสามารถพบได้ในเอกสารประกอบ คำสั่งที่น่าสนใจที่สุดคือ become_user: postgres
. ความจริงก็คือตามค่าเริ่มต้น เฉพาะผู้ใช้ postgres เท่านั้นที่สามารถเข้าถึงฐานข้อมูล postgresql และเฉพาะในเครื่องเท่านั้น คำสั่งนี้อนุญาตให้เราดำเนินการคำสั่งในนามของผู้ใช้รายนี้ (หากเราสามารถเข้าถึงได้แน่นอน)
นอกจากนี้ คุณอาจต้องเพิ่มบรรทัดใน pg_hba.conf เพื่ออนุญาตให้ผู้ใช้ใหม่เข้าถึงฐานข้อมูลได้ ซึ่งสามารถทำได้ในลักษณะเดียวกับที่เราเปลี่ยนการกำหนดค่า nginx
และแน่นอน คุณต้องเพิ่มบทบาท postgresql ให้กับ Playbook หลัก
การติดตั้ง Ruby ผ่าน rbenv
Ansible ไม่มีโมดูลสำหรับการทำงานกับ rbenv แต่ติดตั้งโดยการโคลนที่เก็บ git ดังนั้นปัญหานี้จึงกลายเป็นปัญหาที่ไม่ได้มาตรฐานที่สุด มาสร้างบทบาทให้เธอกันเถอะ /ansible/roles/ruby_rbenv/main.yml
และเริ่มกรอกรายละเอียดกันเลย:
# Install rbenv and ruby
- name: Install rbenv
become_user: "{{ user }}"
git: repo=https://github.com/rbenv/rbenv.git dest=~/.rbenv
เราใช้คำสั่งกลายเป็นผู้ใช้อีกครั้งเพื่อทำงานภายใต้ผู้ใช้ที่เราสร้างขึ้นเพื่อวัตถุประสงค์เหล่านี้ เนื่องจากมีการติดตั้ง rbenv ในโฮมไดเร็กตอรี่ ไม่ใช่แบบโกลบอล และเรายังใช้โมดูล git เพื่อโคลนพื้นที่เก็บข้อมูล โดยระบุ repo และ dest
ต่อไปเราต้องลงทะเบียน rbenv init ใน bashrc และเพิ่ม rbenv ไปยัง PATH ที่นั่น สำหรับสิ่งนี้ เรามีโมดูล lineinfile:
- name: Add rbenv to PATH
become_user: "{{ user }}"
lineinfile:
path: ~/.bashrc
state: present
line: 'export PATH="${HOME}/.rbenv/bin:${PATH}"'
- name: Add rbenv init to bashrc
become_user: "{{ user }}"
lineinfile:
path: ~/.bashrc
state: present
line: 'eval "$(rbenv init -)"'
จากนั้นคุณจะต้องติดตั้ง ruby_build:
- name: Install ruby-build
become_user: "{{ user }}"
git: repo=https://github.com/rbenv/ruby-build.git dest=~/.rbenv/plugins/ruby-build
และสุดท้ายก็ติดตั้ง Ruby สิ่งนี้ทำได้ผ่าน rbenv นั่นคือเพียงแค่ใช้คำสั่ง bash:
- name: Install ruby
become_user: "{{ user }}"
shell: |
export PATH="${HOME}/.rbenv/bin:${PATH}"
eval "$(rbenv init -)"
rbenv install {{ ruby_version }}
args:
executable: /bin/bash
เราพูดว่าคำสั่งใดที่จะดำเนินการและด้วยอะไร อย่างไรก็ตาม ที่นี่เราพบว่า ansible ไม่ได้รันโค้ดที่อยู่ใน bashrc ก่อนที่จะรันคำสั่ง ซึ่งหมายความว่าจะต้องกำหนด rbenv โดยตรงในสคริปต์เดียวกัน
ปัญหาต่อไปเกิดจากการที่คำสั่งเชลล์ไม่มีสถานะจากมุมมองที่เข้าใจได้ นั่นคือจะไม่มีการตรวจสอบอัตโนมัติว่าติดตั้ง Ruby เวอร์ชันนี้หรือไม่ เราทำเองได้:
- name: Install ruby
become_user: "{{ user }}"
shell: |
export PATH="${HOME}/.rbenv/bin:${PATH}"
eval "$(rbenv init -)"
if ! rbenv versions | grep -q {{ ruby_version }}
then rbenv install {{ ruby_version }} && rbenv global {{ ruby_version }}
fi
args:
executable: /bin/bash
สิ่งที่เหลืออยู่คือการติดตั้ง Bundler:
- name: Install bundler
become_user: "{{ user }}"
shell: |
export PATH="${HOME}/.rbenv/bin:${PATH}"
eval "$(rbenv init -)"
gem install bundler
และอีกครั้ง เพิ่มบทบาทของเรา ruby_rbenv ไปยัง playbook หลัก
ไฟล์ที่แชร์
โดยทั่วไป การตั้งค่าจะเสร็จสิ้นที่นี่ ถัดไป สิ่งที่เหลืออยู่คือเรียกใช้ capistrano และมันจะคัดลอกโค้ดเอง สร้างไดเร็กทอรีที่จำเป็นและเปิดแอปพลิเคชัน (หากกำหนดค่าทุกอย่างอย่างถูกต้อง) อย่างไรก็ตาม capistrano มักต้องการไฟล์การกำหนดค่าเพิ่มเติม เช่น database.yml
หรือ .env
สามารถคัดลอกได้เช่นเดียวกับไฟล์และเทมเพลตสำหรับ nginx มีความละเอียดอ่อนเพียงอย่างเดียว ก่อนที่จะคัดลอกไฟล์ คุณต้องสร้างโครงสร้างไดเร็กทอรีสำหรับไฟล์เหล่านั้น มีลักษณะดังนี้:
# Copy shared files for deploy
- name: Ensure shared dir
become_user: "{{ user }}"
file:
path: "{{ app_path }}/shared/config"
state: directory
เราระบุเพียงไดเร็กทอรีเดียวและ ansible จะสร้างพาเรนต์โดยอัตโนมัติหากจำเป็น
ห้องนิรภัยแบบ Ansible
เราได้พบเห็นข้อเท็จจริงแล้วว่าตัวแปรอาจมีข้อมูลลับ เช่น รหัสผ่านของผู้ใช้ หากคุณได้สร้าง .env
ไฟล์สำหรับการสมัครและ database.yml
จะต้องมีข้อมูลสำคัญเช่นนี้มากกว่านี้ เป็นการดีที่จะซ่อนพวกเขาจากการสอดรู้สอดเห็น เพื่อจุดประสงค์นี้จึงถูกนำมาใช้
มาสร้างไฟล์สำหรับตัวแปรกัน /ansible/vars/all.yml
(ที่นี่คุณสามารถสร้างไฟล์ต่างๆ สำหรับกลุ่มโฮสต์ต่างๆ ได้ เช่นเดียวกับในไฟล์คลังข้อมูล: Production.yml, staging.yml ฯลฯ)
ตัวแปรทั้งหมดที่ต้องเข้ารหัสจะต้องถ่ายโอนไปยังไฟล์นี้โดยใช้ไวยากรณ์ yml มาตรฐาน:
# System vars
user_password: 123qweasd
db_password: 123qweasd
# ENV vars
aws_access_key_id: xxxxx
aws_secret_access_key: xxxxxx
aws_bucket: bucket_name
rails_secret_key_base: very_secret_key_base
หลังจากนั้นไฟล์นี้สามารถเข้ารหัสได้ด้วยคำสั่ง:
ansible-vault encrypt ./vars/all.yml
โดยปกติแล้ว เมื่อเข้ารหัส คุณจะต้องตั้งรหัสผ่านสำหรับการถอดรหัส คุณสามารถดูว่ามีอะไรอยู่ในไฟล์หลังจากเรียกใช้คำสั่งนี้
โดย ansible-vault decrypt
ไฟล์สามารถถอดรหัส แก้ไข และเข้ารหัสอีกครั้งได้
คุณไม่จำเป็นต้องถอดรหัสไฟล์เพื่อให้ทำงานได้ คุณจัดเก็บมันแบบเข้ารหัสและเรียกใช้ Playbook ด้วยอาร์กิวเมนต์ --ask-vault-pass
. Ansible จะขอรหัสผ่าน ดึงข้อมูลตัวแปร และดำเนินการงานต่างๆ ข้อมูลทั้งหมดจะยังคงถูกเข้ารหัส
คำสั่งที่สมบูรณ์สำหรับโฮสต์หลายกลุ่มและ ansible vault จะมีลักษณะดังนี้:
ansible-playbook -i inventory ./playbook.yml -l "staging" --ask-vault-pass
แต่ฉันจะไม่ให้ Playbooks และบทบาทฉบับเต็มแก่คุณ โปรดเขียนด้วยตัวเอง เพราะ ansible เป็นเช่นนั้น - ถ้าคุณไม่เข้าใจว่าต้องทำอะไร มันก็จะไม่ทำเพื่อคุณ
ที่มา: will.com