Hata Ayıklama · 10 dk

Sanal Makine IP Alıyor Ama İnternet Yok: Sebebi Docker

Sanal makinem DHCP'den IP alıyor, ana bilgisayara erişiyor ama internete çıkmıyordu. Ağ ayarları, sürücü, NAT modu; hepsi doğruydu. Sebebi Docker'ın güvenlik duvarı kuralı çıktı. Sorunu KVM/libvirt ve virt-manager kullanan herkes yaşayabilir. İnternette en çok önerilen çözümü, yani sanal ağı yeniden başlatmayı denedim ama işe yaramadı. Asıl sebebi paket sayaçları gösterdi: Docker'ın FORWARD zincirindeki policy drop kuralı, sanal makinemin paketlerini NAT aşamasına varmadan siliyordu. Teşhisi adım adım, kalıcı çözümü de systemd servisiyle anlatıyorum.

Fedora yüklü dizüstümde KVM/libvirt üzerinde çalışan bir Windows 10 sanal makinem var; onu virt-manager ile yönetiyorum. Bu sanal makine bir süredir internete çıkamıyordu. Ama sıradan bir “ağ yok” sorunu değildi: sanal makine ağdan düzgün bir IP adresi alıyor, ana bilgisayara erişebiliyor, ağ adaptöründe hiçbir hata göstermiyordu. Buna rağmen hiçbir siteye bağlanamıyordu.

Kontrol ettiğim bütün ayarlar doğruydu. Sorunun sebebi de buydu: ayarlar doğruydu ama başka bir program onları geçersiz kılıyordu.

Bu yazıda sorunu nasıl teşhis ettiğimi, kullandığım komutları tek tek göstererek anlatıyorum. Yanlış çıkan teşhisimi ve internette en çok önerilen çözümün neden işe yaramadığını da yazdım.

Sistemim / Test Ortamı#

  • Ana bilgisayar: Fedora 44, çekirdek 7.1.8, KDE Plasma (Wayland)
  • Sanallaştırma: KVM/libvirt, virt-manager 5.1.0
  • Sanal makine: Windows 10, e1000e ağ kartı, default sanal ağ (NAT)
  • Docker: 29.7.2 (aynı makinede 13 konteyner çalışıyor)
  • Güvenlik duvarı: firewalld devre dışı

Son madde önemli; yazının sonunda buna döneceğim.

Sorun tam olarak neydi?#

Belirtiler şunlardı:

  • Sanal makine açılıyor, Windows sorunsuz çalışıyor.
  • Aygıt Yöneticisi’nde ağ kartı sağlam, sarı ünlem işareti yok.
  • DHCP’den otomatik IP geliyor: 192.168.122.129.
  • Ana bilgisayara, yani sanal ağ geçidine (192.168.122.1) erişiliyor.
  • Ama hiçbir site açılmıyor, Windows Update çalışmıyor.

Bu belirtiler aslında ilk ipucunu veriyor: IP alabilmek, ağ kartının ve sürücüsünün çalıştığını zaten kanıtlar. Çünkü DHCP ile adres alabilmek için sanal makinenin ana bilgisayardaki DHCP sunucusuyla konuşabilmesi gerekir. Demek ki sanal makinenin içinde aramak boşuna; sorun ana bilgisayarın trafiği dışarı yönlendirmesinde.

Adım 1: Sanal makine gerçekten IP almış mı?#

Tahmin yürütmek yerine kanıta baktım. libvirt, dağıttığı IP adreslerinin kaydını tutuyor:

# Sanal ağın DHCP kayıtları — hangi makineye hangi IP verilmiş?
sudo virsh net-dhcp-leases default

Çıktı:

 Expiry Time           MAC address         Protocol   IP address
 2026-08-18 21:42:35   52:54:00:0f:44:80   ipv4       192.168.122.129/24

Kayıt var ve tarihi bugün. Demek ki sanal makine ağa girmiş, DHCP sunucusuyla konuşmuş ve adresini almış. Bu tek satır üç şeyi birden doğruluyor: ağ kartı çalışıyor, sürücü çalışıyor, sanal ağın DHCP sunucusu çalışıyor.

virt-manager arayüzünde de aynı bilgi görünüyordu. NIC ayrıntılarında IP adresi: 192.168.122.129 yazıyordu, ayarlar da eksiksizdi: ağ kaynağı default : NAT, aygıt modeli e1000e, bağlantı durumu etkin.

Adım 2: Ana bilgisayar tarafını elemek#

Sonra ana bilgisayarda sırayla şunlara baktım:

# Sanal ağ köprüsü ayakta mı, IP'si doğru mu?
ip -br addr show virbr0

# IP yönlendirme açık mı? (1 olmalı)
cat /proc/sys/net/ipv4/ip_forward

# Sanal ağ etkin mi?
sudo virsh net-list --all

# Ana bilgisayarın kendi interneti çalışıyor mu?
ping -c2 1.1.1.1

Sonuçlar şöyleydi: virbr0 ayakta ve 192.168.122.1 adresine sahip, yönlendirme açık (1), default ağı etkin ve açılışta otomatik başlıyor, ana bilgisayarın interneti de sorunsuz.

Sanal ağın DHCP sunucusunun günlüğüne de baktım:

sudo journalctl -b -u virtnetworkd | tail -20
dnsmasq-dhcp[7851]: DHCPACK(virbr0) 192.168.122.129 52:54:00:0f:44:80

DHCPACK satırı, sunucunun adresi onayladığını gösteriyor. Yani sanal ağın kendisi de düzgün çalışıyor.

Bu noktada durum şuydu: sanal makine sağlam, sanal ağ sağlam, ana bilgisayarın interneti sağlam. Geriye tek bir ihtimal kalıyordu: paketler ana bilgisayarın içinde, yönlendirme aşamasında siliniyor.

Adım 3: Yanlış teşhis (dürüst not)#

Burada bir hata yapıp zaman kaybettim. Şöyle düşündüm: “libvirt’in NAT kuralı silinmiş olmalı; ağı yeniden başlatırsam kurallarını yeniden kurar.”

İnternette bu soruna en sık önerilen çözüm de zaten bu:

sudo virsh net-destroy default
sudo virsh net-start default

Denedim ama işe yaramadı.

Çünkü kurallara baktığımda libvirt’in NAT kuralının zaten yerinde ve doğru olduğunu gördüm:

sudo nft list chain ip libvirt_network guest_nat
chain guest_nat {
    type nat hook postrouting priority srcnat; policy accept;
    ip saddr 192.168.122.0/24 ip daddr != 192.168.122.0/24 counter packets 0 bytes 0 masquerade
}

Kural doğruydu. Yani yeniden kurulacak eksik bir şey yoktu.

Ama bu çıktıda gözden kaçırdığım bir ayrıntı vardı ve asıl cevap oradaydı: counter packets 0.

Kural doğruydu ama hiçbir paket ona uğramamıştı. Bu, “kural bozuk” demek değil; “paketler buraya varamadan siliniyor” demek.

Buradan şunu öğrendim: bir kuralın var olması, çalıştığı anlamına gelmiyor. Kuralın varlığına değil, sayacına bakmak gerekiyor.

Benzer bir hatayı daha önce de yapmıştım: Fedora’da WiFi yavaşlığı sorununda da ölçüm yapmadan önce iki yanlış çözüm denemiştim.

Adım 4: Asıl sebep — Docker’ın FORWARD zinciri#

Bir paketin sanal makineden internete ulaşması için ana bilgisayarda iki aşamayı geçmesi gerekir:

  1. Yönlendirme izni (forward): “Bu paket bir arayüzden diğerine geçebilir mi?”
  2. Adres çevirisi (NAT/masquerade): “Kaynak adresi ana bilgisayarınkiyle değiştirilsin.”

libvirt’in kuralı ikinci aşamadaydı ve sayacı sıfırdı. Demek ki paketler birinci aşamayı geçemiyordu. Oraya baktım:

sudo nft list table ip filter

Sebebi burada buldum:

# Warning: table ip filter is managed by iptables-nft, do not touch!
table ip filter {
    chain FORWARD {
        type filter hook forward priority filter; policy drop;
        counter packets 5869 bytes 404873 jump DOCKER-USER
        counter packets 5869 bytes 404873 jump DOCKER-FORWARD
    }

    chain DOCKER-FORWARD {
        iifname "br-e88d4b2aacb7" counter packets 0 bytes 0 accept
        iifname "docker0" counter packets 0 bytes 0 accept
        ...
    }

    chain DOCKER-USER {
    }
}

Bu çıktıda dört şey dikkat çekiyor:

  • policy drop şu anlama geliyor: “Listemde açıkça izin verilmeyen her paketi sessizce sil.”
  • FORWARD zincirinden 5.869 paket geçmiş.
  • Docker’ın bütün “izin ver” kurallarının sayacı sıfır.
  • DOCKER-USER zinciri bomboş.

Yani o 5.869 paketin hiçbiri izin alamamış, hepsi policy drop kuralıyla silinmiş. Bu paketler de sanal makinemin internete çıkma denemeleriydi.

Docker’ın izin listesinde yalnızca kendi ağları var: docker0 ve br- ile başlayan konteyner köprüleri. Sanal makinemin köprüsü olan virbr0 bu listede yok; Docker onu tanımadığı için geçirmiyor.

libvirt izin veriyordu, peki neden yetmedi?#

Sebebi şu: modern Linux’ta aynı noktaya birden fazla kural tablosu bağlanabiliyor ve her tablo ayrı ayrı değerlendiriliyor.

libvirt kendi tablosunda (ip libvirt_network) “bu pakete izin ver” diyordu, Docker ise kendi tablosunda (ip filter) “engelle” diyordu. Tablolardan biri paketi silerse, diğerinin izin vermesi bir şey değiştirmiyor.

Bu yüzden yalnızca libvirt’in kurallarına bakmak yanıltıcı oldu; yanlış teşhisimin sebebi de buydu.

Adım 5: Çözüm — DOCKER-USER zincirine muafiyet#

Docker’ın iyi bir yanı var: kullanıcıya DOCKER-USER adında boş bir zincir ayırıyor ve kendi kurallarını yenilerken bu zincire dokunmuyor. Yani elle kural eklemek için ayrılmış yer tam olarak burası.

İki kural ekledim:

# Sanal makineden çıkan trafiğe izin ver
sudo nft insert rule ip filter DOCKER-USER \
  iifname "virbr0" counter accept

# Cevapların geri dönmesine izin ver
sudo nft insert rule ip filter DOCKER-USER \
  oifname "virbr0" ct state established,related counter accept

İkinci kuraldaki ct state established,related ifadesi önemli. Anlamı şu: yalnızca sanal makinenin kendi başlattığı bağlantıların cevapları geçebilir. Dışarıdan içeriye rastgele bağlantı açılmasına izin verilmiyor.

Adım 6: Çözümü kalıcı hale getirmek#

Yukarıdaki kurallar bilgisayarı yeniden başlatınca kaybolur; üstelik Docker her yeniden başladığında da silinebilir. Kalıcı çözüm için küçük bir betik ve bir systemd servisi yazdım.

Önce betik — /usr/local/sbin/libvirt-docker-forward-fix.sh:

#!/usr/bin/env bash
set -u
BR=virbr0

# Docker zincirini kurana kadar bekle (en fazla 30 sn)
for _ in $(seq 30); do
  nft list chain ip filter DOCKER-USER >/dev/null 2>&1 && break
  sleep 1
done
nft list chain ip filter DOCKER-USER >/dev/null 2>&1 || exit 0

mevcut=$(nft list chain ip filter DOCKER-USER 2>/dev/null)

# Kural zaten varsa tekrar ekleme
case "$mevcut" in
  *"oifname \"$BR\" ct state established,related"*) ;;
  *) nft insert rule ip filter DOCKER-USER \
       oifname "$BR" ct state established,related counter accept ;;
esac
case "$mevcut" in
  *"iifname \"$BR\" counter"*) ;;
  *) nft insert rule ip filter DOCKER-USER \
       iifname "$BR" counter accept ;;
esac

Çalıştırma izni verin:

sudo chmod 755 /usr/local/sbin/libvirt-docker-forward-fix.sh

Sonra servis dosyası — /etc/systemd/system/libvirt-docker-forward.service:

[Unit]
Description=libvirt sanal makine trafigini Docker'in engelinden muaf tut
After=docker.service virtnetworkd.service
Wants=docker.service
PartOf=docker.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/sbin/libvirt-docker-forward-fix.sh

[Install]
WantedBy=multi-user.target

Etkinleştirin:

sudo systemctl daemon-reload
sudo systemctl enable --now libvirt-docker-forward.service

Buradaki iki satır kritik:

  • PartOf=docker.service: Docker her yeniden başladığında bu servis de yeniden çalışır, yani kurallar kendiliğinden geri gelir. Bunu koymazsanız tek bir systemctl restart docker komutu sorunu geri getirir.
  • After=docker.service: Bizim kuralımız, Docker kendi kurallarını kurduktan sonra ekleniyor. Aksi hâlde Docker bizimkini ezebilir.

Betiği de aynı kuralı iki kez eklemeyecek şekilde yazdım; defalarca çalışsa bile zincirde kopya kural birikmez.

Adım 7: Doğrulama#

Kuralları ekledikten sonra sayaçlara tekrar baktım:

sudo nft list chain ip filter DOCKER-USER
chain DOCKER-USER {
    iifname "virbr0" counter packets 111302 bytes 5232457 accept
    oifname "virbr0" ct state established,related counter packets 31452 bytes 175087283 accept
}

10 saniye içinde 111 binden fazla paket geçmiş ve sanal makineye 175 MB veri inmişti. Sayaçların bu hızla artması, trafiğin gerçekten aktığını gösteriyordu.

libvirt’in NAT kuralının sayacı da artmaya başlamıştı:

sudo nft list chain ip libvirt_network guest_nat | grep masquerade
... counter packets 21 bytes 1092 masquerade to :1024-65535

Sayaç artık sıfır değil. Paketler yönlendirme aşamasını geçip NAT’a ulaşıyor, sanal makine de internete çıkıyor.

Bu sorun neden herkesin başına gelebilir?#

Sistemimde kurulu paketlerin tarihine baktığımda Docker’ın 8 Ağustos 2026’da 29.7.2 sürümüne güncellendiğini gördüm:

rpm -qa --qf '%{INSTALLTIME:date}  %{NAME}-%{VERSION}\n' | grep docker

Sanal makinedeki internetin kesilmesi de tam bu tarihten sonra başlamıştı.

Sorunu zor fark edilir yapan şey bu: bir gün her şey çalışıyor, ertesi gün siz hiçbir ayara dokunmadığınız hâlde çalışmıyor. Sanal makinenin ayarlarını baştan sona kontrol ediyorsunuz ama orada aramak boşuna, çünkü değişen şey bambaşka bir programın güncellemesi.

Aynı makinede hem Docker hem KVM/libvirt kullanan herkes bu riski taşıyor. firewalld devre dışıysa risk daha da artıyor: firewalld çalışırken libvirt onun arka ucunu kullanır, iki sistem aynı yönetici altında uyum içinde çalışır. Kapalıyken Docker yönlendirme kurallarını tek başına yönetir ve çakışma çok daha kolay ortaya çıkar.

Sık sorulan sorular#

Sanal makinem IP alıyor ama internet yok, ilk neye bakmalıyım?

Ana bilgisayarda sudo nft list table ip filter komutunu çalıştırın. FORWARD zincirinde policy drop görüyorsanız ve sisteminizde Docker kuruluysa sebep büyük ihtimalle budur. Kesin kanıt için sayaçlara bakın: FORWARD zincirinde paket sayısı artarken Docker’ın izin kurallarının hepsi sıfırsa, paketler bu politikayla siliniyor demektir.

Docker’ı kapatsam sorun çözülür mü?

Evet, çözülür. Ancak Docker’ı kapatmak konteyner projelerinizi de durdurur. Yukarıdaki muafiyet kuralı, iki sistemi birlikte çalıştırmanın çok daha pratik yolu.

Bu çözüm sistemimi güvenlik açısından zayıflatır mı?

Hayır. Yalnızca sanal makine köprünüzün (virbr0) trafiğine izin veriyorsunuz, üstelik geri dönüş yönünde sadece kurulmuş bağlantılara. libvirt’in kendi filtreleme kuralları ayrı bir tabloda çalışmayı sürdürüyor, sanal makineler de NAT arkasında korunmaya devam ediyor.

VirtualBox kullanıyorum, bende de olur mu?

VirtualBox’ın NAT modu paketleri kendi içinde işlediği için bu çakışmadan genelde etkilenmez. Sorun özellikle KVM/libvirt tabanlı kurulumlarda (virt-manager, GNOME Boxes) görülür.

Bunun yerine firewalld’i açsam olmaz mı?

Olur, hatta bazı sistemlerde daha temiz bir çözümdür. Ancak çalışan Docker projeleriniz varsa bu geçiş, port yayınlama ve konteyner ağlarında yeni sorunlar çıkarabilir. Ben çalışan bir kurulumu riske atmamak için hedefe yönelik çözümü tercih ettim.

Sonuç#

Bu sorundan çıkardığım ders şu: bir ayarın doğru görünmesi, o ayarın çalıştığı anlamına gelmiyor.

libvirt’in kuralları doğruydu ama başka bir program onları geçersiz kılıyordu. Kuralın yerinde olduğunu görmek beni yanlış yöne götürdü; doğru cevabı sayaçlar verdi.

Bir kuralın sayacı sıfırsa, o kural ne kadar doğru yazılmış olursa olsun çalışmıyor demektir. Güvenlik duvarı sorunlarında ilk bakılacak yer burası.

Aynı yöntemi Fedora donma sorununu çözerken de kullanmıştım: tahmin etmek yerine loglara ve sayılara bakmak, sorunu çok daha hızlı buldurdu.

Hızlı kontrol komutları (özet)#

# 1) Sanal makine IP almış mı? (almışsa sanal makine ve sanal ağ sağlam demektir)
sudo virsh net-dhcp-leases default

# 2) Docker yönlendirmeyi engelliyor mu? ("policy drop" + izinler sıfırsa evet)
sudo nft list table ip filter

# 3) libvirt'in NAT kuralı paket görüyor mu? (sıfırsa paketler oraya varamıyor)
sudo nft list chain ip libvirt_network guest_nat

# 4) Çözümü uygula
sudo nft insert rule ip filter DOCKER-USER iifname "virbr0" counter accept
sudo nft insert rule ip filter DOCKER-USER oifname "virbr0" ct state established,related counter accept

# 5) Kalıcılığı doğrula
systemctl is-enabled libvirt-docker-forward.service

Aynı sorunu yaşadıysanız yorumlarda anlatın; hangi Docker sürümünde karşılaştığınızı yazarsanız başkalarına da yol gösterir.

Yorumlar

Yorumlar GitHub hesabıyla yapılır. “Göster”e tıklayınca Giscus (giscus.app) yüklenir.