SSD Nodes Learn
Rehberler Matt ConnorYazan Matt Connor · Güncellendi 2026-07-24

Ansible ile VPS Kurulum Rehberi

Ubuntu 24.04 üzerinde pipx ile Ansible kurulumu, envanter oluşturma ve VPS sıkılaştırma adımları. Permission denied ve sudo hataları için çözüm yolları.

Ne inşa ediliyor

Ansible yüklü bir kontrol makinesi ve üzerinde yalnızca standart imaj bulunan bir veya daha fazla yeni Ubuntu 24.04 VPS. İşlem sonunda, sunucuları tanımlayan bir envanter dosyası, kimlik doğrulamanın uçtan uca çalıştığını kanıtlayan bir ad-hoc ping ve tüm yeni-VPS kontrol listesini kod olarak çalıştıran bir playbook elde edilecektir: SSH anahtarınızın tanımlandığı bir deploy kullanıcısı, sıkılaştırılmış sshd, fail2ban, unattended upgrades ve her şeyi engellemeden önce OpenSSH'a izin veren bir güvenlik duvarı. Bu işlemi bir sunucu veya yirmi sunucu üzerinde uygulayabilirsiniz. Playbook'u iki kez çalıştırın; ikinci çalıştırma hiçbir şeyi değiştirmez, asıl amaç budur.

On beş yıl boyunca VPS kurulumu yaptıktan sonra size gerçek durumu söyleyebilirim: Herkes ilk beş sunucuyu manuel olarak kurar, ardından altıncı sunucu için bir hafta sonunu kaybeder çünkü kimse ilk beş sunucuda ne yaptığını hatırlamaz. Bu kılavuz, birden fazla Linux sunucusunu yönetme konusundaki incelemeyi derinleştirir — aynı apt install komutunu üç farklı terminale yazarken kendinizi yakaladığınız gün bu kılavuzu incelemeye başlayın.

Ansible'ın gerçekte ne olduğu, tek bir paragrafta

Ansible agentless bir yapıdır. Yönetilen sunuculara kurulacak bir daemon bulunmaz: kontrol makinesi standart SSH üzerinden bağlanır, hedef sisteme küçük bir Python modülü kopyalar, modülü çalıştırır, yazdırılan JSON çıktısını okur ve modülü siler. Hedef sistemin ihtiyaç duyduğu tek şey, her standart Ubuntu imajında halihazırda bulunan python3 bileşenidir. Önemli olan kavram idempotent terimidir ve bu basit bir anlama gelir: bir görev bir eylemi değil, bir durumu tanımlar. Bir paket için state: present kullanımı, "kurucuyu çalıştır" demek değil, "bu paketin kurulu olduğundan emin ol" demektir. Eğer istenen durum zaten sağlanmışsa, Ansible hiçbir işlem yapmaz ve durumu changed yerine ok olarak raporlar. Bu özellik ürünün temelidir; bir playbook'un tekrar çalıştırılmasını güvenli kılan ve güvenli tekrarlar sayesinde bir shell script'in altyapı yönetim aracına dönüşmesini sağlayan budur.

Ön Koşullar ve dikkat edilmesi gereken noktalar

  • Bir kontrol makinesi: dizüstü bilgisayarınız veya küçük bir VPS. Ubuntu 24.04 varsayılmaktadır; macOS üzerinde Homebrew ile pipx kurulduğunda aynı şekilde çalışır.
  • KVM üzerinde Ubuntu 24.04 çalıştıran ve root olarak erişilebilen bir veya daha fazla hedef VPS. Bu makinelere herhangi bir kurulum yapılmayacaktır.
  • Her hedef için SSH key kimlik doğrulaması. Ansible, ssh komutunuzun yetkisiyle aynı yetkiye sahiptir; eğer ssh root@host parola sorarsa Ansible hata verir.
  • Ubuntu 24.04 üzerinde pip install ansible, error: externally-managed-environment nedeniyle çalışmaz. Bu bir hata değil, dağıtım politikasıdır. pipx kullanın.
  • YAML boşluk yapısı sözdizimidir. Yanlış girinti mapping values are not allowed in this context hatasına yol açar ve herhangi bir yerdeki tab karakteri işlemi durdurur.
  • Playbook sshd yapılandırmasını sıkılaştırırken her hedefte açık bir SSH oturumu tutun. Müşterilerin kurtarılmasına yardımcı olduğum her durumda, "temiz bir test yapmak" amacıyla mevcut oturumun kapatılması soruna neden olmuştur.

Adım 1: Ansible'ı kontrol makinesine pip yerine pipx ile kurun

Klasik yaklaşım pip3 install ansible yöntemidir. Eğer sistem kurulumu bir adım erken hata verirse — Command 'pip3' not found, but can be installed with: sudo apt install python3-pip — ve sadece pip kurulursa, gerçek sorunla karşılaşırsınız:

pip3 install ansible
error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
    python3-xyz, where xyz is the package you are trying to
    install.

Ubuntu 24.04, sistem Python'ını harici olarak yönetilen (PEP 668) olarak işaretler; bu nedenle pip, aynı dosyalar için apt ile çakışamaz. --break-system-packages komutunu kullanmayın; bayrak ismi durumu açıkça belirtmektedir. Doğru çözüm pipx kullanımıdır; pipx, Ansible için izole bir virtualenv oluşturur ve binary dosyalarını PATH değişkenine ekler:

sudo apt update && sudo apt install -y pipx
pipx ensurepath
pipx install --include-deps ansible

PATH değişikliğinin uygulanması için pipx ensurepath işleminden sonra yeni bir shell açın. --include-deps sadece bir görsel öğe değildir: ansible paketi kendi başına konsol scriptleri içermez — ansible, ansible-playbook ve diğerleri ansible-core bağımlılığının giriş noktalarıdır — bu nedenle flag kullanılmazsa pipx, No apps associated with package ansible or its dependencies hatasıyla kurulumu reddeder. Ayrıca çıplak ansible-core yerine ansible paketini kurun; tam paket topluluk koleksiyonlarını içerir ve bu playbook bunlardan ikisini (ansible.posix ve community.general) kullanır.

ansible --version

Doğru sonuç, ansible [core 2.19.x] gibi bir satırla başlar ve çalıştırılan Python sürümünü belirtir; mevcut herhangi bir çekirdek sürüm bu işlemler için uygundur. ansible: command not found durumu ise ~/.local/bin öğesinin henüz PATH değişkeninizde olmadığı anlamına gelir — yeni bir shell açın veya source ~/.bashrc uygulayın.

Kurulum bu kadardır. Hedef makinelerde herhangi bir işlem yapılmaz.

Step 2: Her hedef için SSH anahtarı erişimi

ssh-keygen -t ed25519 -C "ansible control"
ssh-copy-id root@10.0.0.10
ssh-copy-id root@10.0.0.20

Ardından, her ana makine için bir kez şu komutu çalıştırarak doğrulayın:

ssh root@10.0.0.10 true && echo ok

Bu tek satırlık komut iki işlev görür: anahtar tabanlı kimlik doğrulamanın şifresiz çalıştığını onaylar ve ana makine anahtarını known_hosts dosyasına kaydeder. Bu işlemi hemen yapın; çünkü Ansible, kaydedilmemiş bir ana makine anahtarını, işlem sırasında donma gibi görünen etkileşimli bir istem olarak sunar.

Step 3: envanter — Küçük yapılar için INI, büyüyen yapılar için YAML

Envanter, Ansible'ın erişebileceği makinelerin listelendiği bir metin dosyasıdır. Yeni bir proje dizininde inventory.ini dosyasını oluşturun:

[vps]
web1 ansible_host=10.0.0.10
web2 ansible_host=10.0.0.20

[vps:vars]
ansible_user=root

web1 sizin belirlediğiniz bir takma addır; çıktılarda görünen ve --limit web1 ile hedeflediğiniz isimdir. ansible_host gerçek adrestir. [vps] bir gruptur ve [vps:vars] bu gruptaki her host için değişkenleri belirler; ansible_user ise Ansible'ın giriş yaptığı kullanıcıdır. Yanındaki ansible.cfg sayesinde -i ifadesini tekrar yazmanıza gerek kalmaz:

[defaults]
inventory = inventory.ini

Ansible, ansible.cfg dosyasını mevcut dizinden okur. YAML formatındaki aynı envanter —dosyayı inventory.yml olarak kaydedin ve ansible.cfg parametresini bu isme yönlendirin— host başına düşen değişken sayısı arttığında tercih edeceğiniz yöntemdir:

vps:
  hosts:
    web1:
      ansible_host: 10.0.0.10
    web2:
      ansible_host: 10.0.0.20
  vars:
    ansible_user: root

Bu iki format eşdeğerdir. İki sunucu için INI formatı daha kolaydır; yirmi sunucu için ise YAML daha ölçeklenebilirdir. Birini seçin ve daha fazla üzerinde düşünmeyin.

Adım 4: ad-hoc komutlar — her şeyi kanıtlayan yeşil pong

ansible all -m ping

Bu bir ICMP değildir. ping modülü tam bir prova niteliğindedir: SSH girişi, modül kopyalama, hedef üzerinde Python yürütme ve temizlik işlemleri gerçekleştirilir. Doğru sonuç, her ana bilgisayar için bir blok halinde yeşil renktedir:

web1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}

Yeşil SUCCESS; kimlik doğrulama, Python yorumlayıcısı ve taşıma katmanının çalıştığını, dolayısıyla playbook'un da çalışacağını gösterir. Kırmızı UNREACHABLE!; herhangi bir modül çalıştırılmadan önce taşıma katmanının başarısız olduğunu gösterir; hata metni ve çözüm yöntemi aşağıdaki hata modları bölümünde yer almaktadır. Bilinmesi gereken iki ad-hoc komut daha:

ansible all -a "uptime"
ansible all -m apt -a "update_cache=true upgrade=dist" --become

Ad-hoc kullanımı tek seferlik işlemler ve kontroller içindir. İki kez çalıştırılacak her işlem bir playbook içinde yer almalıdır.

Step 5: ilk playbook — yeni-VPS kontrol listesi kod olarak

Bu, yeni bir sunucuda ilk on dakikada manuel olarak yapacağınız işlemlerin tamamıdır. site.yml olarak kaydedin:

---
- name: Baseline a fresh Ubuntu VPS
  hosts: vps
  become: true

  vars:
    deploy_user: deploy
    deploy_pubkey: "{{ lookup('file', '~/.ssh/id_ed25519.pub') }}"
    baseline_packages:
      - fail2ban
      - unattended-upgrades
      - ufw
    baseline_services:
      - fail2ban
      - unattended-upgrades

  tasks:
    - name: Create the deploy user
      ansible.builtin.user:
        name: "{{ deploy_user }}"
        groups: sudo
        append: true
        shell: /bin/bash

    - name: Install the deploy user's SSH key
      ansible.posix.authorized_key:
        user: "{{ deploy_user }}"
        key: "{{ deploy_pubkey }}"

    - name: Passwordless sudo for the deploy user
      ansible.builtin.copy:
        dest: /etc/sudoers.d/deploy
        content: "{{ deploy_user }} ALL=(ALL) NOPASSWD:ALL\n"
        mode: "0440"
        validate: /usr/sbin/visudo -cf %s

    - name: Install baseline packages
      ansible.builtin.apt:
        name: "{{ baseline_packages }}"
        state: present
        update_cache: true

    - name: Enable and start baseline services
      ansible.builtin.service:
        name: "{{ item }}"
        state: started
        enabled: true
      loop: "{{ baseline_services }}"

    - name: Harden sshd with a drop-in
      ansible.builtin.copy:
        dest: /etc/ssh/sshd_config.d/00-hardening.conf
        content: |
          PasswordAuthentication no
          KbdInteractiveAuthentication no
          PermitRootLogin prohibit-password
          X11Forwarding no
        mode: "0644"
        validate: /usr/sbin/sshd -t -f %s
      notify: Restart ssh

    - name: Allow OpenSSH through ufw
      community.general.ufw:
        rule: allow
        name: OpenSSH

    - name: Enable ufw with default deny
      community.general.ufw:
        state: enabled
        policy: deny

  handlers:
    - name: Restart ssh
      ansible.builtin.service:
        name: ssh
        state: restarted

Kopyalamak yerine anlaşılması gereken satırlar:

Variables (değişkenler) vars: altında bulunur ve "{{ deploy_user }}" ile referans verilir — eğer bir değer süslü parantez ile başlıyorsa, YAML ayrıştırıcısının yanlış okumaması için tüm ifadeyi tırnak içine alın. lookup('file', ...), çalışma zamanında kamu anahtarınızı control makinesinden okur, bu nedenle playbook anahtar materyali taşımaz.

The loop. loop: "{{ baseline_services }}" servis görevini her öğe için bir kez çalıştırır ve çıktı her öğeyi kendi satırında gösterir. apt görevinin paket listesinin tamamını tek seferde aldığını unutmayın; tek bir apt işlemi daha hızlıdır ve paketler için tercih edilen yöntemdir; döngüler (loops) ise gerçek anlamda her seferinde tek bir öğe üzerinde işlem yapan modüller içindir.

The handler kavramsallaştırılması gereken temel konudur. notify: Restart ssh "ssh'ı şimdi yeniden başlat" anlamına gelmez. Handler'ı kuyruğa alır; handler oyunun sonunda ve yalnızca bildiren görev gerçekten changed raporlarsa çalışır. Playbook'u yarın tekrar çalıştırın: drop-in dosyası zaten doğrudur, kopyalama görevi ok raporlar ve sshd asla yeniden başlatılmaz. validate: satırı emniyet kilididir — sshd, eski dosyanın yerine yenisini koymadan önce dosyayı kontrol eder; böylece bir yazım hatası daemon'ı bozmak yerine görevin hata vermesine neden olur.

PermitRootLogin prohibit-password, no değil — bilinçli olarak. Bu playbook, bir anahtar ile root olarak giriş yapar. prohibit-password, sizin anahtarınız aktif kalırken root için parola ile girişi kapatır. Deploy kullanıcısı doğrulandıktan sonra (ssh deploy@10.0.0.10 sudo trueweb1 sadece Ansible'ın bildiği bir takma ad olduğundan düz adresi kullanın), envanterdeki ansible_user=deploy öğesini değiştirin ve sonraki bir çalıştırmada bunu no olarak sıkılaştırın. Sizi sistemin dışında bırakmayacak bir sırayla sıkılaştırma yapın.

00- öneki önemlidir. Çoğu anahtar kelime için sshd, ayrıştırdığı ilk örneği kabul eder ve Ubuntu'nun sshd_config dosyası, kendi gövdesinden önce sözlük sırasına göre sshd_config.d/*.conf içerir. Ubuntu 24.04 cloud imajları o dizinde zaten bir 60-cloudimg-settings.conf ile gelir ve cloud-init aracılığıyla parola ile girişe izin veren sağlayıcılar PasswordAuthentication yes ile bir 50-cloud-init.conf ekler; bizimkini 00-hardening.conf olarak adlandırmak, onun en üstte sıralanmasını ve her ikisini de geçmesini sağlar.

Task order (görev sırası) güvenlik bariyeridir. Allow OpenSSH, reddetme politikasıyla Enable ufw'den önce çalışır — Ansible görevleri listelendiği sırayla kesin olarak yürütür, bu nedenle duvar örülmeden önce boşluk oluşur. fail2ban'ın burada kullanışlı olması için herhangi bir yapılandırmaya ihtiyacı yoktur; Ubuntu varsayılanları sshd'yi kutudan çıktığı haliyle izler; jail'lerin gerçekte ne yaptığı ve nelerin ayarlanması gerektiği fail2ban on Ubuntu 24.04 guide içerisinde kapsanmaktadır.

Step 6: --check ile dry run yapın, ardından işlemi gerçek olarak çalıştırın

ansible-playbook site.yml --check

Check mode bağlantı kurar, yapılacak işlemleri hesaplar ve hiçbir şeyi değiştirmez. En alttaki PLAY RECAP içindeki changed= sayısını kontrol edin; bu, her bir host üzerinde değiştirilecek görevlerin sayısıdır. Önemli bir kısıtlama: Bir sonraki görev, önceki bir görevin değişikliklerine bağlıysa check mode yapısal bir sınıra sahiptir. Ubuntu'nun standart server imajı ufw ile birlikte gelir, bu nedenle bu playbook dry-run aşamasını sorunsuz tamamlar; ancak ufw bulunmayan minimal bir imajda, ufw görevleri check mode aşamasında hata verir. Bunun sebebi check mode'un paketi gerçekten yüklememiş olması ve modülün çağıracak bir şey bulamamasıdır. Bu bir hata değil, dry run işleminin bir kısıtlamasıdır. Plan doğru göründüğünde:

ansible-playbook site.yml

Her görev her host için bir satır yazdırır — sarı changed, yeşil ok — ve özet şu şekilde olmalıdır:

PLAY RECAP *********************************************************************
web1 : ok=10  changed=9  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0
web2 : ok=10  changed=9  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0

On ok, veri toplama işlemi, sekiz görev ve handler toplamıdır. changed değeriniz benimkinden bir veya iki farklı olabilir: Ubuntu'nun standart imajı ufw ve unattended-upgrades ile birlikte gelir; ayrıca fail2ban, apt tarafından yüklendiği an kendini başlatır, bu nedenle bir görev ilk çalışmasında bile ok raporlayabilir — yani durumun zaten sağlandığını beyan eder. Sıfır olması gereken sayılar unreachable ve failed'dir. become: true hakkında bir not: root olarak bağlandığınız sürece bir formalitedir, ancak ansible_user değerini deploy olarak değiştirdiğiniz an sudo devreye girer; bu playbook tarafından yüklenen NOPASSWD sudoers dosyası, -K ifadesinin komut satırında görünmesini engeller. Bu olmazsa, aşağıda açıklanan Missing sudo password hatası alınır.

Step 7: komutu iki kez çalıştırın — idempotence örneği

Aynı komutu hemen tekrar çalıştırın:

web1 : ok=9  changed=0  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0

changed=0 ve ok değerleri bir azalmıştır; çünkü bildirilmemiş handler hiç çalışmamıştır. Hiçbir şey yeniden kurulmadı, sshd yeniden başlatılmadı, ufw üzerinde işlem yapılmadı. Bu durum, playbook'u bir sağlayıcı (provisioner) olduğu kadar bir denetleyici (audit) yapar: gelecek ay envantere web3 ekleyin ve tekrar çalıştırın; yeni sunucu oluşturulur, eski sunucular doğrulanır. Dokunulmamış bir sunucuda sıfırdan farklı bir changed değeri, sapma (drift) anlamına gelir; bu durum, playbook yerine birinin manuel olarak düzenleme yaptığını gösterir.

Buradan itibaren desen katlanarak devam eder. Yazılmaya değer bir sonraki playbook, aynı VPS üzerinde WireGuard VPN kurar ve ufw kuralını SSH'ın sadece tünel üzerinden yanıt vermesini sağlayacak şekilde sıkılaştırır; sonrasında ise her uygulama sunucusuna Docker ve Compose kuran bir playbook gelir. site.yml üç ekran uzunluğuna ulaştığında, onu rollere bölün — ancak ondan önce değil.

Hata modları ve karşılaşılacak hata metinleri

UNREACHABLE with Permission denied.

web1 | UNREACHABLE! => {
    "changed": false,
    "msg": "Failed to connect to the host via ssh: root@10.0.0.10: Permission denied (publickey).",
    "unreachable": true
}

SSH ile bağlantı kurulamadı; herhangi bir modül çalıştırılmadan hata oluştu: ansible_user hatalı, anahtar ilgili hosta kopyalanmadı veya yanlış anahtar sunuluyor. Hatayı ssh root@10.0.0.10 ile, ardından sunulan anahtarları görmek için ssh -v ile yeniden oluşturun. Şifre ile SSH bağlantısı sağlanıyor ancak Ansible sağlanamıyorsa, ssh-copy-id adımı atlanmış demektir.

Missing sudo password.

web1 | FAILED! => {
    "msg": "Missing sudo password"
}

become: true ayarlandı, root olmayan bir kullanıcı ile bağlantı kuruldu ve bu kullanıcının sudo için şifreye ihtiyacı var. Komut satırına -K (--ask-become-pass) ekleyin veya kullanıcıya NOPASSWD sudoers girişi tanımlayın; playbook, siz kullanıcı değiştirmeden önce deploy için tam olarak bu işlemi gerçekleştirir.

error: externally-managed-environment. Ubuntu 24.04 üzerindeki sistem Python'ı için pip çalıştırıldı. 1. adımda açıklandı: pipx kullanılmalıdır, pip veya --break-system-packages değil.

mapping values are not allowed in this context.

ERROR! Syntax Error while loading YAML.
  mapping values are not allowed in this context

Neredeyse her zaman girinti (indentation) hatasıdır: yanlış derinlikte bir anahtar veya iki nokta üst üste işaretinden sonra eksik boşluk. Rapor edilen satır numarası hatanın tam yerini değil, hataya yakın bir noktayı gösterir; bir üst satırı da kontrol edin. Benzer bir hata olan found character '\t' that cannot start any token, bir tab karakterinin kullanıldığını gösterir; YAML buna izin vermez. Her çalıştırmadan önce ansible-playbook site.yml --syntax-check işlemini alışkanlık haline getirin ve editörünüzü YAML için iki boşluklu girintiye ayarlayın.

/usr/bin/python3: not found. Standart Ubuntu 24.04 imajlarında nadirdir, ancak minimal veya netboot imajlarında yaygındır: modül yürütme işlemi başarısız olur çünkü hedef sistemde Python bulunmamaktadır. Hedef tarafta hiçbir gereksinim duymayan tek modül olan raw ile sistemi hazırlayın: ansible all -m raw -a "apt-get update && apt-get install -y python3" --become, ardından playbook'u tekrar çalıştırın.

FAQ

Ansible'ı yönetilen sunuculara kurmam gerekiyor mu?

Hayır. Ansible agentless bir yapıdadır: kontrol makinesi SSH üzerinden küçük Python modülleri gönderir, bunları çalıştırır ve ardından kaldırır. Hedef makinede sadece python3 ve SSH erişimi gereklidir; standart Ubuntu imajları her ikisine de sahiptir. Bu kılavuzdaki tek kurulum kontrol makinenizde gerçekleştirilir.

Ansible neden "Permission denied (publickey)" hatası verir?

Permission denied (publickey) içeren UNREACHABLE! bloğu, Ansible herhangi bir işlem yapmadan önce SSH kimlik doğrulamasının başarısız olduğu anlamına gelir. Envanterdeki ansible_user kısmının kurduğunuz hesapla eşleştiğinden, ilgili ana makineye ssh-copy-id ile bağlandığınızdan ve düz ssh user@host komutunun şifresiz giriş yaptığından emin olun. Düz ssh komutunu düzelten her çözüm Ansible'ı da düzeltecektir, çünkü her ikisi de aynı taşıma protokolünü kullanır.

Ansible'da idempotent ne anlama gelir?

Bir görev, gerçekleştirilecek bir eylem yerine istenen bir durumu tanımlar — "bu paket mevcuttur", "bu satır bu dosyadadır" gibi. Eğer durum zaten sağlanmışsa, Ansible hiçbir işlem yapmaz ve changed yerine ok raporlar. Bu nedenle bir playbook iki kez çalıştırıldığında ikinci seferde changed=0 sonucu alınır; yeniden çalıştırma işlemi riskli bir yeniden kurulum değil, güvenli bir denetimdir.

Ubuntu 24.04 üzerinde Ansible kurmak için pip mi yoksa pipx mi kullanmalıyım?

pipx. Ubuntu 24.04, sistem Python'ını harici olarak yönetilen olarak işaretler, bu nedenle pip install ansible işlemi tasarımdan dolayı error: externally-managed-environment hatası verir. pipx install --include-deps ansible, Ansible'ı izole bir virtualenv içine kurar; ansible, ansible-playbook ve diğerlerini PATH üzerinde temiz bir şekilde sunar.

ansible ve ansible-core paketleri arasındaki fark nedir?

ansible-core, motor ve yalnızca ansible.builtin modüllerini içerir. ansible paketi ise çekirdek yapıyı, küratörlüğünü topluluk koleksiyonlarının yaptığı paketlerle birleştirir — bu paketler arasında bu kılavuzda kullanılan ansible.posix (authorized_key modülü) ve community.general (ufw modülü) bulunur. Tam paketi kullanarak başlayın; yalnızca bir nedeniniz olduğunda çekirdek ve seçilmiş koleksiyonlar ile yapıyı sadeleştirin.