Ansible ile Python Test Ortamlarını Hazırlamak
Selamlar, bu yazıda biraz can sıkıcı ama bir o kadar da hayat kurtaran bir konuya bakacağız: birden fazla test sunucusunda Python ortamını tutarlı tutmak. Tek makinede python -m venv yetiyor, evet. Ama üç-beş runner, bir de staging kutusu eklendiğinde 'benim makinemde geçiyordu' tiyatrosu başlıyor. Ansible burada gerçekten işin tonunu değiştiriyor, hadi bir bakalım.
Neden el ile yapmamak gerekiyor?
Açıkçası ilk başlarda ben de 'küçük bir bash script yeter' diye düşünmüştüm. İki sunucuda bile bu inanç çatlıyor: birinde python3.11, diğerinde python3.10, üçüncüsünde libffi-dev eksik, sonra bir kütüphane kaynaktan derlenmeye çalışıyor ve siz logların başında bir saat geçiriyorsunuz. Ansible'ın güzel tarafı, her görevin idempotent olması: aynı playbook'u on kez çalıştırın, yine aynı sonuca varır. Yani 'acaba bozar mıyım' korkusu olmadan koşturursunuz.
Envanter ile başlayalım
Önce hangi makinelere dokunacağımızı netleştirmemiz lazım. Basit bir inventory/hosts dosyası işi görüyor:
[test_runners]
runner01 ansible_host=10.0.2.21
runner02 ansible_host=10.0.2.22
[test_runners:vars]
ansible_user=ci
ansible_python_interpreter=/usr/bin/python3
Buradaki ansible_python_interpreter küçük ama önemli bir ayrıntı. Hedef sistemde Python 2 ile 3 birlikteyse Ansible hangisini kullanacağını şaşırabiliyor; biz açık açık söyleyince mesele kapanıyor.
Sistem bağımlılıkları ve venv
Test ortamının temeli iki parçadan oluşuyor: sistem paketleri (derleme için gerekenler) ve proje seviyesinde bir virtualenv. Bunları tek bir playbook'ta toplayalım:
---
- name: Python test ortamini hazirla
hosts: test_runners
become: true
vars:
proje_adi: <Proj>
proje_dizini: /opt/{{ proje_adi }}
proje_kullanici: ci
venv_dizini: /opt/{{ proje_adi }}/venv
tasks:
- name: Sistem paketlerini kur
ansible.builtin.package:
name:
- python3
- python3-venv
- python3-dev
- build-essential
- libssl-dev
- libffi-dev
- git
state: present
- name: Proje dizinini olustur
ansible.builtin.file:
path: '{{ proje_dizini }}'
state: directory
owner: '{{ proje_kullanici }}'
group: '{{ proje_kullanici }}'
mode: '0755'
- name: Venv olustur ve pip'i guncelle
ansible.builtin.pip:
virtualenv: '{{ venv_dizini }}'
virtualenv_command: python3 -m venv
name: pip
state: latest
become_user: '{{ proje_kullanici }}'
- name: Test bagimliliklarini kur
ansible.builtin.pip:
virtualenv: '{{ venv_dizini }}'
requirements: '{{ proje_dizini }}/requirements-test.txt'
become_user: '{{ proje_kullanici }}'
Burada dikkat çekmek istediğim nokta become_user kullanımı. Venv'i root ile oluşturursanız sonradan CI kullanıcısı write izni alamaz ve pip install patlar. Ben bu hatayı bir kez yapmıştım, sonra bir daha yapmadım.
pytest ve coverage'ı işin içine sokmak
Test ortamı sadece 'paketleri kurduk' demek değil; çalıştırma komutunu da standartlaştırmalıyız. requirements-test.txt dosyasının içine en azından şunlar girer:
pytest>=8.0
pytest-cov>=5.0
pytest-xdist>=3.5
coverage[toml]>=7.4
Sonra projenin pyproject.toml dosyasına pytest'in nereye bakacağını yazıyoruz:
- name: pyproject.toml icin pytest ayari yerlestir
ansible.builtin.blockinfile:
path: '{{ proje_dizini }}/pyproject.toml'
marker: '# {mark} ANSIBLE PYTEST AYARI'
block: |
[tool.pytest.ini_options]
addopts = '-ra -q --cov=src --cov-report=xml --cov-report=term'
testpaths = ['tests']
blockinfile modülü, dosyayı tamamen ezmeden sadece kendi bloğunu yönetiyor. Yani projenizdeki diğer ayarlar yerinde kalıyor. Kanaatim odur ki üretim playbook'larında template yerine blockinfile/lineinfile kullanmak, başkalarının yaptığı el değişikliklerine biraz nezaket göstermek demek.
CI tarafına bağlamak
Playbook'u CI'da çalıştırmak için karmaşık bir orkestrasyona girmeye gerek yok. GitHub Actions örneği:
- name: Test ortamini hazirla
run: |
ansible-playbook -i inventory/hosts playbook.yml \
--limit test_runners \
--extra-vars 'proje_adi={{ github.event.repository.name }}'
- name: Testleri kosturlar uzerinde calistir
run: |
ansible test_runners -i inventory/hosts -m shell \
-a 'cd /opt/{{ proje_adi }} && venv/bin/pytest -n auto'
Önce --check --diff ile bir provadan geçirmenizi öneriyorum. Hiçbir değişiklik yapmadan size 'şunu şuna çevirecektim' diye söylüyor; production öncesi paha biçilmez bir alışkanlık.
Sık karşılaşılan tuzaklar
- Venv'i root ile oluşturmak: Sonra CI kullanıcısı pip install çalıştıramaz.
become_userher zaman ayar. - Sistem Python'una
pip installyapmak:--break-system-packagesile zorlamayın; venv kullanın, distro'nun python'unu temiz bırakın. pipgörevini her seferindestate: latestile çağırmak: requirements dosyası varsa ona güvenin, sürümleri orada sabitleyin. Aksi halde test sonuçları rastgele kütüphane güncellemeleri yüzünden değişir ve nedenini bulamazsınız.gather_facts: falseile başlamak: Hız için cazip görünüyor ama paket yöneticisi modülü dağıtım bilgisini bekliyor;packageyerineaptveyadnfyazmaya başlarsanız taşınabilirlik gider.
Doğrulama
Çalışıp çalışmadığını anlamanın en hızlı yolu:
ansible test_runners -i inventory/hosts -m shell \
-a 'venv/bin/python -c "import pytest, coverage; print(pytest.__version__, coverage.__version__)"'
Tüm runner'lardan aynı sürümü görüyorsanız iş tamam.
Kapanış
Bu yazıda Ansible ile Python test ortamlarını kurmanın iskeletine baktık; envanter, venv, pytest ayarı ve CI bağlantısı. Bence bu kadar otomatize edilmiş bir akış kurulduktan sonra geri dönüp el ile sunucu hazırlamak istemezsiniz, ben de istemiyorum. Umarım faydalı olmuştur, bir sonraki yazıda görüşmek üzere.
