تكوين خادم لنشر تطبيق ريلز باستخدام Ansible

منذ وقت ليس ببعيد كنت بحاجة إلى كتابة العديد من أدلة التشغيل Ansible لإعداد الخادم لنشر تطبيق Rails. والمثير للدهشة أنني لم أجد دليلًا بسيطًا خطوة بخطوة. لم أكن أرغب في تقليد قواعد اللعبة الخاصة بشخص آخر دون فهم ما كان يحدث، وفي النهاية كان عليّ قراءة الوثائق، وجمع كل شيء بنفسي. ربما يمكنني مساعدة شخص ما في تسريع هذه العملية بمساعدة هذه المقالة.

أول شيء يجب أن تفهمه هو أن ansible يوفر لك واجهة ملائمة لتنفيذ قائمة محددة مسبقًا من الإجراءات على خادم (خوادم) بعيد عبر SSH. لا يوجد سحر هنا، فلا يمكنك تثبيت مكون إضافي والحصول على نشر بدون توقف لتطبيقك مع عامل الإرساء والمراقبة والأشياء الجيدة الأخرى خارج الصندوق. من أجل كتابة كتاب قواعد اللعبة، يجب أن تعرف ما تريد القيام به بالضبط وكيفية القيام بذلك. ولهذا السبب فأنا غير راضٍ عن كتيبات اللعب الجاهزة من GitHub، أو مقالات مثل: "انسخ وشغل، ستنجح".

ماذا نحتاج؟

كما قلت من قبل، لكي تكتب كتاب قواعد اللعبة، عليك أن تعرف ما تريد القيام به وكيفية القيام بذلك. دعونا نقرر ما نحتاج إليه. بالنسبة لتطبيق Rails، سنحتاج إلى العديد من حزم النظام: nginx، وpostgresql (redis، وما إلى ذلك). وبالإضافة إلى ذلك، نحن بحاجة إلى نسخة محددة من روبي. من الأفضل تثبيته عبر rbenv (rvm، asdf...). يعد تشغيل كل هذا كمستخدم جذري دائمًا فكرة سيئة، لذلك تحتاج إلى إنشاء مستخدم منفصل وتكوين حقوقه. بعد ذلك، تحتاج إلى تحميل الكود الخاص بنا إلى الخادم، ونسخ تكوينات nginx، وpostgres، وما إلى ذلك، وبدء تشغيل كل هذه الخدمات.

ونتيجة لذلك، فإن تسلسل الإجراءات هو كما يلي:

  1. تسجيل الدخول كجذر
  2. تثبيت حزم النظام
  3. إنشاء مستخدم جديد، وتكوين الحقوق، ومفتاح ssh
  4. تكوين حزم النظام (nginx وما إلى ذلك) وتشغيلها
  5. نقوم بإنشاء مستخدم في قاعدة البيانات (يمكنك إنشاء قاعدة بيانات على الفور)
  6. تسجيل الدخول كمستخدم جديد
  7. تثبيت rbenv وروبي
  8. تثبيت المجمع
  9. تحميل كود التطبيق
  10. إطلاق خادم بوما

علاوة على ذلك، يمكن تنفيذ المراحل الأخيرة باستخدام capistrano، على الأقل خارج الصندوق، يمكنه نسخ التعليمات البرمجية إلى أدلة الإصدار، وتبديل الإصدار باستخدام رابط رمزي عند النشر الناجح، ونسخ التكوينات من دليل مشترك، وإعادة تشغيل puma، وما إلى ذلك. كل هذا يمكن القيام به باستخدام Ansible، ولكن لماذا؟

هيكل الملف

Ansible لديه صارمة هيكل الملف لجميع ملفاتك، لذا من الأفضل الاحتفاظ بها كلها في دليل منفصل. علاوة على ذلك، ليس من المهم جدًا ما إذا كان سيكون في تطبيق Rails نفسه، أو بشكل منفصل. يمكنك تخزين الملفات في مستودع git منفصل. شخصيًا، وجدت أنه من الأنسب إنشاء دليل غير مرئي في الدليل /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 إنها لا تعرف أي مضيفين يطابقون القائمة بأكملها. يجب أن تكون مدرجة في خاص ملف المخزون.

لنقم بإنشائه في نفس الدليل غير المعقول:

123.123.123.123

هذه هي الطريقة التي نحدد بها المضيف ببساطة (من الأفضل أن يكون مضيف VPS الخاص بنا للاختبار، أو يمكنك تسجيل مضيف محلي) وحفظه تحت الاسم inventory.
يمكنك تجربة تشغيل ansible باستخدام ملف الجرد:

ansible-playbook ./playbook.yml -i inventory
PLAY [Simple Playbook] ************************************************************************************************************************************

TASK [Gathering Facts] ************************************************************************************************************************************

PLAY RECAP ************************************************************************************************************************************

إذا كان لديك وصول ssh إلى المضيف المحدد، فسيقوم ansible بالاتصال وجمع المعلومات حول النظام البعيد. (المهمة الافتراضية [جمع الحقائق]) وبعد ذلك ستقدم تقريرًا قصيرًا عن التنفيذ (PLAY RECAP).

افتراضيًا، يستخدم الاتصال اسم المستخدم الذي قمت بتسجيل الدخول من خلاله إلى النظام. على الأرجح لن يكون على المضيف. في ملف دليل التشغيل، يمكنك تحديد المستخدم الذي سيتم استخدامه للاتصال باستخدام توجيه Remote_user. كما أن المعلومات المتعلقة بالنظام البعيد قد تكون في كثير من الأحيان غير ضرورية بالنسبة لك ويجب ألا تضيع الوقت في جمعها. يمكن أيضًا تعطيل هذه المهمة:

---
- name: Simple playbook
  hosts: all
  remote_user: root
  become: true
  gather_facts: no

حاول تشغيل قواعد اللعبة مرة أخرى وتأكد من أن الاتصال يعمل. (إذا قمت بتحديد المستخدم الجذر، فأنت بحاجة أيضًا إلى تحديد التوجيه أصبح: صحيحًا للحصول على حقوق مرتفعة. كما هو مكتوب في الوثائق: become set to ‘true’/’yes’ to activate privilege escalation. على الرغم من أنه ليس من الواضح تمامًا السبب).

ربما ستتلقى خطأً ناجمًا عن عدم قدرة ansible على تحديد مترجم Python، ومن ثم يمكنك تحديده يدويًا:

ansible_python_interpreter: /usr/bin/python3 

يمكنك معرفة مكان وجود بايثون باستخدام الأمر whereis python.

تثبيت حزم النظام

يتضمن التوزيع القياسي لـ Ansible العديد من الوحدات للعمل مع حزم النظام المختلفة، لذلك لا يتعين علينا كتابة نصوص bash لأي سبب من الأسباب. نحتاج الآن إلى إحدى هذه الوحدات لتحديث النظام وتثبيت حزم النظام. لدي Ubuntu Linux على 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

المهمة هي بالضبط المهمة التي سيؤديها Ansible على الخوادم البعيدة. نعطي المهمة اسمًا حتى نتمكن من تتبع تنفيذها في السجل. ونحن نصف، باستخدام بناء جملة وحدة معينة، ما يجب القيام به. في هذه الحالة apt: update_cache=yes - يقول لتحديث حزم النظام باستخدام الوحدة النمطية apt. الأمر الثاني أكثر تعقيدًا بعض الشيء. نقوم بتمرير قائمة الحزم إلى الوحدة apt ونقول أنها كذلك state ينبغي أن تصبح presentأي أننا نقول تثبيت هذه الحزم. وبطريقة مماثلة، يمكننا أن نطلب منهم حذفها، أو تحديثها بمجرد التغيير state. يرجى ملاحظة أنه لكي تعمل القضبان مع postgresql، نحتاج إلى حزمة postgresql-contrib، التي نقوم بتثبيتها الآن. مرة أخرى، عليك أن تعرف هذا وتفعله، لأن insible لن يفعل هذا من تلقاء نفسه.

حاول تشغيل قواعد اللعبة مرة أخرى وتأكد من تثبيت الحزم.

إنشاء مستخدمين جدد.

للعمل مع المستخدمين، يحتوي Ansible أيضًا على وحدة نمطية - user. دعونا نضيف مهمة أخرى (لقد قمت بإخفاء الأجزاء المعروفة بالفعل من دليل اللعب خلف التعليقات حتى لا أقوم بنسخها بالكامل في كل مرة):

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

نقوم بإنشاء مستخدم جديد، وتعيين Schell وكلمة المرور له. ومن ثم نواجه عدة مشاكل. ماذا لو كانت أسماء المستخدمين يجب أن تكون مختلفة بالنسبة للمضيفين المختلفين؟ وتخزين كلمة المرور بنص واضح في قواعد اللعبة يعد فكرة سيئة للغاية. في البداية، دعونا نضع اسم المستخدم وكلمة المرور في متغيرات، وفي نهاية المقالة سأوضح كيفية تشفير كلمة المرور.

---
- 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 (الرئيسي هو الملف الرئيسي الذي سيتم تحميله وتنفيذه عندما يكون الدور متصلاً بدليل التشغيل؛ ويمكن توصيل ملفات الأدوار الأخرى به). يمكنك الآن نقل جميع المهام المتعلقة بالمستخدم إلى هذا الملف:

# 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

في دليل التشغيل الرئيسي، يجب عليك تحديد كيفية استخدام دور المستخدم:

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

إذا أطلقنا الآن قواعد اللعبة الخاصة بنا، فسوف تؤدي المهام المحددة لكلا المضيفين. ولكن في الوقت نفسه، بالنسبة للمضيف المرحلي، ستكون المتغيرات مختلفة عن متغيرات الإنتاج، وليس فقط في الأدوار وقواعد اللعب، ولكن أيضًا في تكوينات nginx. {{ inventory_hostname }} لا تحتاج إلى أن تكون محددة في ملف المخزون - هذا متغير غير مقبول خاص ويتم تخزين المضيف الذي يعمل عليه دليل التشغيل حاليًا هناك.
إذا كنت تريد أن يكون لديك ملف مخزون لعدة مضيفين، ولكنك تريد تشغيله لمجموعة واحدة فقط، فيمكن القيام بذلك باستخدام الأمر التالي:

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

خيار آخر هو أن يكون لديك ملفات مخزون منفصلة لمجموعات مختلفة. أو يمكنك الجمع بين الطريقتين إذا كان لديك العديد من المضيفين المختلفين.

دعنا نعود إلى إعداد nginx. بعد نسخ ملفات التكوين، نحتاج إلى إنشاء رابط رمزي في 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. إذا كانت المجموعة موجودة بالفعل، أو كانت حزمة النظام مثبتة بالفعل، فسيقوم 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 إلى قواعد اللعبة الرئيسية.

إعداد 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 إلى قواعد اللعبة الرئيسية.

تثبيت روبي عبر 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 لاستنساخ المستودع، وتحديد الريبو والوجهة.

بعد ذلك، نحتاج إلى تسجيل 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

وأخيرا تثبيت روبي. ويتم ذلك من خلال 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 مباشرة في نفس البرنامج النصي.

المشكلة التالية ترجع إلى حقيقة أن أمر الصدفة ليس له حالة من وجهة نظر غير معقولة. أي أنه لن يكون هناك فحص تلقائي لمعرفة ما إذا كان هذا الإصدار من روبي مثبتًا أم لا. يمكننا أن نفعل هذا بأنفسنا:

- 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 إلى قواعد اللعبة الرئيسية.

الملفات المشتركة.

بشكل عام، يمكن إكمال الإعداد هنا. بعد ذلك، كل ما تبقى هو تشغيل 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 تلقائيًا بإنشاء المجلدات الرئيسية إذا لزم الأمر.

أنسبل قبو

لقد واجهنا بالفعل حقيقة أن المتغيرات يمكن أن تحتوي على بيانات سرية مثل كلمة مرور المستخدم. إذا كنت قد خلقت .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 يمكن فك تشفير الملف وتعديله ثم تشفيره مرة أخرى.

لا تحتاج إلى فك تشفير الملف للعمل. يمكنك تخزينه مشفرًا وتشغيل قواعد اللعبة باستخدام الوسيطة --ask-vault-pass. سيطلب Ansible كلمة المرور، وسيقوم باسترداد المتغيرات، وتنفيذ المهام. ستبقى جميع البيانات مشفرة.

سيبدو الأمر الكامل لعدة مجموعات من المضيفين وansible vault كما يلي:

ansible-playbook -i inventory ./playbook.yml -l "staging" --ask-vault-pass

لكنني لن أعطيك النص الكامل لكتب اللعب والأدوار، فاكتبه بنفسك. لأن ansible هكذا - إذا لم تفهم ما يجب عليك فعله، فلن يقوم بذلك نيابةً عنك.

المصدر: www.habr.com

إضافة تعليق