การตั้งค่าเซิร์ฟเวอร์เพื่อปรับใช้แอปพลิเคชัน Rails โดยใช้ Ansible

ไม่นานมานี้ ฉันจำเป็นต้องเขียน Playbooks Ansible หลายเล่มเพื่อเตรียมเซิร์ฟเวอร์สำหรับการปรับใช้แอปพลิเคชัน Rails และน่าประหลาดใจที่ฉันไม่พบคู่มือง่ายๆ ทีละขั้นตอนเลย ฉันไม่ต้องการคัดลอก Playbook ของคนอื่นโดยไม่เข้าใจสิ่งที่เกิดขึ้น และท้ายที่สุด ฉันต้องอ่านเอกสารและรวบรวมทุกอย่างด้วยตัวเอง บางทีฉันสามารถช่วยบางคนเร่งกระบวนการนี้ให้เร็วขึ้นได้ด้วยความช่วยเหลือของบทความนี้

สิ่งแรกที่ต้องทำความเข้าใจก็คือ ansible มอบอินเทอร์เฟซที่สะดวกสบายให้กับคุณเพื่อดำเนินการรายการการกระทำที่กำหนดไว้ล่วงหน้าบนเซิร์ฟเวอร์ระยะไกลผ่าน SSH ไม่มีเวทย์มนตร์ที่นี่ คุณไม่สามารถติดตั้งปลั๊กอินและทำให้แอปพลิเคชันของคุณต้องหยุดทำงานเป็นศูนย์ด้วยนักเทียบท่า การตรวจสอบ และสารพัดอื่น ๆ ทันที เพื่อที่จะเขียน Playbook คุณต้องรู้ว่าคุณต้องการทำอะไรและต้องทำอย่างไร นั่นเป็นเหตุผลที่ฉันไม่พอใจกับ Playbook สำเร็จรูปจาก GitHub หรือบทความเช่น: “คัดลอกแล้วเรียกใช้ มันจะได้ผล”

เราต้องการอะไร

อย่างที่ผมบอกไปแล้ว เพื่อที่จะเขียน Playbook คุณต้องรู้ว่าคุณต้องการทำอะไรและต้องทำอย่างไร มาตัดสินใจว่าเราต้องการอะไร สำหรับแอปพลิเคชัน Rails เราจำเป็นต้องมีแพ็คเกจระบบหลายอย่าง: nginx, postgresql (redis ฯลฯ) นอกจากนี้ เราต้องการทับทิมเวอร์ชันเฉพาะ วิธีที่ดีที่สุดคือติดตั้งผ่าน rbenv (rvm, asdf...) การเรียกใช้ทั้งหมดนี้ในฐานะผู้ใช้รูทนั้นเป็นความคิดที่ไม่ดีเสมอไป ดังนั้นคุณต้องสร้างผู้ใช้แยกต่างหากและกำหนดค่าสิทธิ์ของเขา หลังจากนี้ คุณจะต้องอัปโหลดโค้ดของเราไปยังเซิร์ฟเวอร์ คัดลอกการกำหนดค่าสำหรับ nginx, postgres ฯลฯ และเริ่มบริการเหล่านี้ทั้งหมด

เป็นผลให้ลำดับของการกระทำมีดังนี้:

  1. เข้าสู่ระบบในฐานะรูท
  2. ติดตั้งแพ็คเกจระบบ
  3. สร้างผู้ใช้ใหม่ กำหนดค่าสิทธิ์ คีย์ ssh
  4. กำหนดค่าแพ็คเกจระบบ (nginx ฯลฯ ) และเรียกใช้
  5. เราสร้างผู้ใช้ในฐานข้อมูล (คุณสามารถสร้างฐานข้อมูลได้ทันที)
  6. เข้าสู่ระบบในฐานะผู้ใช้ใหม่
  7. ติดตั้ง rbenv และ ruby
  8. การติดตั้งบันเดิล
  9. กำลังอัพโหลดรหัสแอปพลิเคชัน
  10. เปิดตัวเซิร์ฟเวอร์ Puma

ยิ่งไปกว่านั้น ขั้นตอนสุดท้ายสามารถทำได้โดยใช้ capistrano อย่างน้อยก็สามารถคัดลอกโค้ดลงในไดเร็กทอรี release สลับรีลีสด้วย symlink เมื่อปรับใช้สำเร็จ คัดลอกการกำหนดค่าจากไดเร็กทอรีที่แชร์ รีสตาร์ท puma ฯลฯ ทั้งหมดนี้สามารถทำได้โดยใช้ Ansible แต่ทำไม?

โครงสร้างไฟล์

Ansible มีความเข้มงวด โครงสร้างไฟล์ สำหรับไฟล์ทั้งหมดของคุณ ดังนั้นจึงควรเก็บทั้งหมดไว้ในไดเร็กทอรีแยกต่างหาก ยิ่งไปกว่านั้น ไม่สำคัญว่าจะอยู่ในแอปพลิเคชันรางหรือแยกกัน คุณสามารถจัดเก็บไฟล์ไว้ในที่เก็บ git แยกต่างหากได้ โดยส่วนตัวแล้ว ฉันพบว่าสะดวกที่สุดในการสร้างไดเร็กทอรีที่เข้าใจได้ในไดเร็กทอรี /config ของแอปพลิเคชัน Rails และจัดเก็บทุกอย่างไว้ในที่เดียว

คู่มือการเล่นที่เรียบง่าย

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 }} ไม่จำเป็นต้องระบุในไฟล์สินค้าคงคลัง - นี่ ตัวแปรที่สามารถวัดได้พิเศษ และโฮสต์ที่ playbook กำลังทำงานอยู่จะถูกเก็บไว้ที่นั่น
หากคุณต้องการมีไฟล์สินค้าคงคลังสำหรับหลายโฮสต์ แต่เรียกใช้สำหรับกลุ่มเดียวเท่านั้น สามารถทำได้โดยใช้คำสั่งต่อไปนี้:

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

เพิ่มความคิดเห็น