پیکربندی یک سرور برای استقرار یک برنامه Rails با استفاده از Ansible

چندی پیش لازم بود چندین کتاب بازی Ansible بنویسم تا سرور را برای استقرار یک برنامه Rails آماده کنم. و با کمال تعجب، من یک کتابچه راهنمای گام به گام ساده پیدا نکردم. من نمی‌خواستم کتاب بازی شخص دیگری را بدون اینکه بفهمم چه اتفاقی می‌افتد کپی کنم، و در نهایت مجبور شدم اسناد را بخوانم و همه چیز را خودم جمع‌آوری کنم. شاید بتوانم با کمک این مقاله به کسی کمک کنم تا این روند را سرعت بخشد.

اولین چیزی که باید درک کنید این است که ansible یک رابط کاربری مناسب برای انجام یک لیست از پیش تعریف شده از اقدامات روی سرور(های) راه دور از طریق SSH در اختیار شما قرار می دهد. هیچ جادویی در اینجا وجود ندارد، شما نمی توانید یک پلاگین را نصب کنید و برنامه خود را با داکر، مانیتورینگ و سایر موارد مفید، بدون استفاده از برنامه خود نصب کنید. برای نوشتن یک کتاب بازی، باید بدانید که دقیقا می خواهید چه کاری انجام دهید و چگونه آن را انجام دهید. به همین دلیل است که من از کتاب‌های بازی آماده GitHub یا مقالاتی مانند: «کپی و اجرا، کار می‌کند» راضی نیستم.

آنچه ما نیاز داریم؟

همانطور که قبلاً گفتم، برای نوشتن کتاب بازی باید بدانید که می خواهید چه کاری انجام دهید و چگونه آن را انجام دهید. بیایید تصمیم بگیریم به چه چیزی نیاز داریم. برای یک برنامه Rails به چندین بسته سیستمی نیاز داریم: nginx، postgresql (redis و غیره). علاوه بر این، ما به یک نسخه خاص از یاقوت نیاز داریم. بهتر است آن را از طریق rbenv (rvm, asdf...) نصب کنید. اجرای همه اینها به عنوان یک کاربر ریشه همیشه ایده بدی است، بنابراین باید یک کاربر جداگانه ایجاد کنید و حقوق او را پیکربندی کنید. پس از این، شما باید کد ما را در سرور آپلود کنید، تنظیمات مربوط به nginx، postgres و غیره را کپی کنید و همه این خدمات را شروع کنید.

در نتیجه، توالی اقدامات به شرح زیر است:

  1. به عنوان root وارد شوید
  2. نصب بسته های سیستمی
  3. ایجاد یک کاربر جدید، پیکربندی حقوق، کلید ssh
  4. بسته های سیستمی (nginx و غیره) را پیکربندی کرده و آنها را اجرا کنید
  5. ما یک کاربر در پایگاه داده ایجاد می کنیم (شما می توانید بلافاصله یک پایگاه داده ایجاد کنید)
  6. به عنوان یک کاربر جدید وارد شوید
  7. rbenv و ruby ​​را نصب کنید
  8. نصب باندلر
  9. در حال آپلود کد برنامه
  10. راه اندازی سرور پوما

علاوه بر این، آخرین مراحل را می‌توان با استفاده از capistrano انجام داد، حداقل می‌تواند کد را در فهرست‌های انتشار کپی کند، پس از استقرار موفقیت‌آمیز، نسخه را با یک پیوند نمادین تغییر دهد، تنظیمات را از یک فهرست مشترک کپی کند، پوما را مجدداً راه‌اندازی کند و غیره. همه اینها را می توان با استفاده از Ansible انجام داد، اما چرا؟

ساختار فایل

Ansible سختگیرانه دارد ساختار فایل برای همه فایل‌هایتان، بنابراین بهتر است همه آن‌ها را در یک فهرست جداگانه نگه دارید. علاوه بر این، خیلی مهم نیست که در خود برنامه ریل باشد یا جداگانه. شما می توانید فایل ها را در یک مخزن git جداگانه ذخیره کنید. من شخصاً ایجاد یک دایرکتوری ansible در فهرست /config برنامه rails و ذخیره همه چیز در یک مخزن را راحت‌تر دیدم.

کتاب بازی ساده

Playbook یک فایل yml است که با استفاده از نحو خاص، توضیح می‌دهد که Ansible چه کاری و چگونه باید انجام دهد. بیایید اولین کتاب بازی را ایجاد کنیم که هیچ کاری انجام نمی دهد:

---
- name: Simple playbook
  hosts: all

در اینجا ما به سادگی می گوییم که کتاب بازی ما نامیده می شود 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 را اجرا کنید و مطمئن شوید که اتصال کار می کند. (اگر کاربر ریشه را مشخص کرده اید، برای به دست آوردن حقوق بالاتر، باید دستور تبدیل: true را نیز مشخص کنید. همانطور که در مستندات نوشته شده است: become set to ‘true’/’yes’ to activate privilege escalation. اگرچه کاملاً مشخص نیست که چرا).

شاید شما خطای ناشی از این واقعیت را دریافت کنید که ansible نمی تواند مفسر پایتون را تعیین کند، سپس می توانید آن را به صورت دستی مشخص کنید:

ansible_python_interpreter: /usr/bin/python3 

با دستور می توانید بفهمید پایتون کجاست whereis python.

نصب پکیج های سیستمی

توزیع استاندارد Ansible شامل ماژول های زیادی برای کار با بسته های سیستمی مختلف است، بنابراین به هر دلیلی مجبور نیستیم اسکریپت های bash بنویسیم. حال برای به روز رسانی سیستم و نصب بسته های سیستمی به یکی از این ماژول ها نیاز داریم. من لینوکس اوبونتو را روی VPS خود دارم، بنابراین برای نصب بسته ها از آن استفاده می کنم apt-get и ماژول برای آن. اگر از سیستم عامل دیگری استفاده می کنید، ممکن است به یک ماژول متفاوت نیاز داشته باشید (به یاد داشته باشید، در ابتدا گفتم که باید از قبل بدانیم چه کاری و چگونه انجام خواهیم داد). با این حال، نحو به احتمال زیاد مشابه خواهد بود.

بیایید کتاب بازی خود را با اولین وظایف تکمیل کنیم:

---
- 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

Task دقیقاً همان کاری است که Ansible روی سرورهای راه دور انجام می دهد. ما به کار یک نام می دهیم تا بتوانیم اجرای آن را در گزارش ردیابی کنیم. و ما با استفاده از نحو یک ماژول خاص، آنچه را که باید انجام دهد، توضیح می دهیم. در این مورد apt: update_cache=yes - می گوید برای به روز رسانی بسته های سیستم با استفاده از ماژول apt. دستور دوم کمی پیچیده تر است. لیستی از بسته ها را به ماژول apt ارسال می کنیم و می گوییم که هستند state باید تبدیل شود presentیعنی می گوییم این بسته ها را نصب کنید. به روشی مشابه، می‌توانیم به آن‌ها بگوییم که آن‌ها را حذف کنند، یا با تغییر ساده آن‌ها را به‌روزرسانی کنند state. لطفاً توجه داشته باشید که برای اینکه ریل ها با postgresql کار کنند به بسته postgresql-contrib نیاز داریم که اکنون در حال نصب آن هستیم. باز هم، شما باید این کار را بدانید و انجام دهید؛ اما به تنهایی این کار را انجام نخواهد داد.

دوباره playbook را اجرا کنید و بررسی کنید که بسته ها نصب شده باشند.

ایجاد کاربران جدید.

برای کار با کاربران، Ansible یک ماژول - کاربر نیز دارد. بیایید یک کار دیگر اضافه کنیم (من بخش های شناخته شده کتاب بازی را پشت نظرات پنهان کردم تا هر بار به طور کامل آن را کپی نکنم):

---
- name: Simple playbook
  # ...
  tasks:
    # ...
    - name: Add a new user
      user:
        name: my_user
        shell: /bin/bash
        password: "{{ 123qweasd | password_hash('sha512') }}"

ما یک کاربر جدید ایجاد می کنیم، یک شل و رمز عبور برای آن تنظیم می کنیم. و سپس با مشکلات متعددی مواجه می شویم. اگر لازم باشد نام های کاربری برای هاست های مختلف متفاوت باشد چه؟ و ذخیره رمز عبور به صورت متن واضح در کتاب بازی ایده بسیار بدی است. برای شروع، اجازه دهید نام کاربری و رمز عبور را در متغیرها قرار دهیم و در پایان مقاله نحوه رمزگذاری رمز عبور را نشان می دهم.

---
- name: Simple playbook
  # ...
  tasks:
    # ...
    - name: Add a new user
      user:
        name: "{{ user }}"
        shell: /bin/bash
        password: "{{ user_password | password_hash('sha512') }}"

متغیرها در کتاب های بازی با استفاده از بریس های مجعد دوتایی تنظیم می شوند.

مقادیر متغیرها را در فایل موجودی نشان خواهیم داد:

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 و آن را در لیست کلیدهای مجاز برای کاربر در سرور آپلود می کند.

نقش ها

هر سه وظیفه برای ایجاد استفاده را می توان به راحتی در یک گروه از وظایف طبقه بندی کرد و بهتر است این گروه را جدا از کتاب اصلی ذخیره کنید تا بیش از حد بزرگ نشود. برای این منظور 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

اکنون به فایل ها و قالب ها نیاز داریم. تفاوت بین آنها این است که ansible فایل ها را به طور مستقیم کپی می کند. و قالب ها باید پسوند j2 را داشته باشند و می توانند با استفاده از همان بریس های دوبل فرفری از مقادیر متغیر استفاده کنند.

بیایید nginx in را فعال کنیم 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 قبل از کپی در قالب جایگزین می شود. اگر از یک کتاب بازی برای گروه های مختلف میزبان استفاده می کنید، این کار مفید است. به عنوان مثال، ما می توانیم فایل موجودی خود را اضافه کنیم:

[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 در حال حاضر برای آن در حال اجرا است در آنجا ذخیره می شود.
اگر می خواهید یک فایل موجودی برای چندین هاست داشته باشید، اما فقط برای یک گروه اجرا شود، این کار را می توان با دستور زیر انجام داد:

ansible-playbook -i inventory ./playbook.yml -l "staging"

گزینه دیگر این است که فایل های موجودی جداگانه برای گروه های مختلف داشته باشید. یا اگر میزبان های مختلف زیادی دارید، می توانید این دو رویکرد را با هم ترکیب کنید.

بیایید به راه اندازی nginx برگردیم. پس از کپی کردن فایل های پیکربندی، باید از sites_available یک Symlink در sitest_enabled به my_app.conf ایجاد کنیم. و 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 است. اگر گروه از قبل وجود داشته باشد، یا بسته سیستم قبلاً نصب شده باشد، 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 اصلی اضافه کنید.

نصب روبی از طریق 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

ما دوباره از دستورالعمل be_user استفاده می کنیم تا تحت کاربری که برای این اهداف ایجاد کرده ایم کار کنیم. از آنجایی که 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 باید مستقیماً در همان اسکریپت تعریف شود.

مشکل بعدی به این دلیل است که دستور shell از نظر قابل تشخیص حالت ندارد. یعنی هیچ بررسی خودکاری وجود نخواهد داشت که آیا این نسخه از 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

تنها چیزی که باقی می ماند نصب باندلر است:

- 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

اما متن کامل کتاب‌های بازی و نقش‌ها را به شما نمی‌دهم، خودتان بنویسید. زیرا ansible چنین است - اگر نمی‌دانید چه کاری باید انجام شود، آن را برای شما انجام نمی‌دهد.

منبع: www.habr.com

اضافه کردن نظر