itsrda

Raspberry Pi'de Flask Uygulamasını systemd Servisi Olarak Çalıştırmak

Terminal kapanınca ölen Flask uygulamasını, elektrik kesintisinden sonra kendiliğinden ayağa kalkan bir servise dönüştürmenin adım adım yolu: gunicorn, systemd birimi, günlükler ve sık yapılan hatalar.

Bir Flask uygulamasını python app.py ile başlatıp terminali açık bırakmak, denemeler için yeterlidir. Gerçek bir iş için, örneğin bir ofiste her gün kullanılan bir panel için, bu yöntem iki gün içinde sorun çıkarır: SSH oturumu kapanır, elektrik gidip gelir, süreç bellek yetmediği için ölür ve kimse fark etmez. Çözüm, uygulamayı işletim sisteminin kendi hizmet yöneticisi olan systemd'ye teslim etmektir.

Bu yazıda Raspberry Pi OS (Debian tabanlı) üzerinde bir Flask uygulamasını gunicorn ile çalıştırıp systemd servisi haline getireceğiz. Aynı adımlar herhangi bir Debian veya Ubuntu sunucuda da geçerlidir.

1. Neden Flask'in kendi sunucusu değil?

flask run ve app.run() geliştirme sunucusunu başlatır. Resmi belgeler de bunun üretimde kullanılmaması gerektiğini söyler: tek iş parçacıklıdır, hata ayıklayıcı açık kalırsa uzaktan kod çalıştırmaya kapı aralar ve çökme durumunda kendini toparlamaz. Üretimde gunicorn gibi bir WSGI sunucusu kullanılır.

Bir Raspberry Pi 3 gibi sınırlı bellekli (1 GB) bir cihazda 2 ya da 3 işçi (worker) süreci çoğu iç panel için fazlasıyla yeterlidir.

2. Sanal ortam ve gunicorn

Uygulama dizininde ayrı bir sanal ortam kurmak, sistem Python paketlerini kirletmez ve güncellemelerde sürpriz yaşatmaz.

cd ~/uygulama
python3 -m venv venv
source venv/bin/activate
pip install flask gunicorn

Önce elle deneyin. app:app ifadesi "app.py dosyasındaki app nesnesi" demektir:

venv/bin/gunicorn --workers 2 --bind 127.0.0.1:5000 app:app

Başka bir terminalden curl -I http://127.0.0.1:5000/ ile yanıt geldiğini doğrulayın. Çalışıyorsa Ctrl+C ile durdurun.

3. systemd birim dosyası

/etc/systemd/system/uygulama.service dosyasını oluşturun:

[Unit]
Description=Uygulama (Flask + gunicorn)
After=network-online.target mariadb.service
Wants=network-online.target

[Service]
User=pi
Group=pi
WorkingDirectory=/home/pi/uygulama
EnvironmentFile=-/home/pi/uygulama/.env
ExecStart=/home/pi/uygulama/venv/bin/gunicorn --workers 2 --bind 127.0.0.1:5000 app:app
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Satırların anlamı:

  • After / Wants: Ağ ve veritabanı hazır olmadan başlama. Elektrik kesintisinden sonra Pi açıldığında servis veritabanından önce kalkarsa ilk istekler hata verir; After= bunu önler.
  • User: Servisi root olarak çalıştırmayın. Uygulamanın yalnızca kendi dizinine erişmesi yeterlidir.
  • EnvironmentFile: Parolaları ve gizli anahtarları kod yerine bir .env dosyasında tutun. Baştaki - işareti, dosya yoksa hata vermemesini sağlar. Dosyanın iznini chmod 600 .env ile kısıtlayın.
  • Restart=on-failure: Süreç beklenmedik şekilde ölürse 5 saniye sonra yeniden başlatır. systemctl stop ile bilerek durdurduğunuzda yeniden başlatmaz.
  • NoNewPrivileges / PrivateTmp: Ucuz ama etkili sertleştirme ayarlarıdır.

4. Etkinleştirme ve doğrulama

sudo systemctl daemon-reload
sudo systemctl enable --now uygulama
systemctl status uygulama --no-pager

enable servisi açılışta başlatır, --now ise hemen başlatır. Durum çıktısında active (running) görmelisiniz.

5. Günlükleri okumak

systemd, servisin çıktısını journal'a yazar:

journalctl -u uygulama -n 50 --no-pager     # son 50 satır
journalctl -u uygulama -f                    # canlı izleme
journalctl -u uygulama --since "1 hour ago"  # son bir saat

Hata ayıklarken en çok işe yarayan komut budur. Uygulama başlamıyorsa neredeyse her zaman sebebi son birkaç satırda yazar.

6. Kod güncellemesi sonrası

Python kodu bellekte yüklü olduğundan dosyayı değiştirmek yetmez; servisi yeniden başlatmalısınız:

sudo systemctl restart uygulama

Jinja şablonları da çoğu kurulumda önbelleğe alınır. Bir HTML şablonunu değiştirip sonucu görmüyorsanız aynı komutu çalıştırın.

7. Sık yapılan hatalar

Belirti Olası neden Çözüm
status=203/EXEC ExecStart yolu yanlış veya dosya çalıştırılabilir değil Tam yolu kontrol edin, ls -l ile bakın
ModuleNotFoundError Paket sistemde değil sanal ortamda ExecStart'ta venv/bin/gunicorn kullanın
Açılışta veritabanı hatası Servis veritabanından önce başladı After=mariadb.service ekleyin
Permission denied Dosya sahibi farklı chown -R pi:pi ~/uygulama
Sürekli yeniden başlıyor Kod hata veriyor journalctl -u uygulama -n 100

8. Sonraki adım: dışarıya açmadan önce

Gunicorn'u doğrudan internete açmak iyi fikir değildir. Önüne bir ters vekil (nginx ya da Caddy) koymak veya Cloudflare Tunnel kullanmak, TLS ve istek sınırlaması gibi işleri sizin yerinize halleder. Bu konuyu Cloudflare Tunnel yazımızda ele aldık.

Özetle: sanal ortam, gunicorn, kısa bir birim dosyası ve enable --now. Bu dört adımla uygulamanız elektrik kesintisinden sonra da kimse müdahale etmeden geri döner.

Örnek birim dosyası ve güvenlik ayarları

Sürekli çalışan bir uygulama için birim dosyası şu mantıkta yazılır:

[Unit]
Description=Panel (gunicorn)
After=network.target mariadb.service

[Service]
User=panel
Group=panel
WorkingDirectory=/home/panel/uygulama
EnvironmentFile=/etc/panel.env
ExecStart=/home/panel/uygulama/venv/bin/gunicorn -w 2 -b 127.0.0.1:8000 app:app
Restart=on-failure
RestartSec=3
NoNewPrivileges=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Dikkat edilecek noktalar: servis root ile çalışmamalı; ayrı bir kullanıcı kullanın. Gizli bilgiler (EnvironmentFile) yalnızca o kullanıcının okuyabileceği izinde olmalı (chmod 600). Uygulamayı 127.0.0.1'e bağlamak, yalnızca ters vekil ya da tünel üzerinden erişilmesini sağlar (Cloudflare Tunnel rehberi). NoNewPrivileges ve PrivateTmp gibi sertleştirme ayarları, olası bir açığın etkisini sınırlar. Küçük bir Pi'de işçi sayısını (-w) 2 ile sınırlayın; fazla süreç belleği tüketir.

Güncelleme ve geri dönüş düzeni

Kodu canlıda doğrudan değiştirmek riskli. Basit ve güvenli bir düzen: (1) mevcut klasörün tarihli bir kopyasını alın; (2) yeni dosyaları yerine koyun; (3) Python sözdizimini ve şablonları test edin (python -m py_compile); (4) servisi yeniden başlatın; (5) bir sağlık adresine curl ile istek atın; (6) başarısızsa kopyayı geri koyup tekrar başlatın. Bu sırayı betikleştirin; ellerle yapılan geri dönüş, stres altındayken hata yapmaya açıktır. Gerçek bir otel panelinde her güncelleme bu kalıpla yapılır ve birkaç kez şablon hatası dakikalar içinde geri alınmıştır (mimari notu).

Sorun giderme tablosu

Belirti Olası neden Bakılacak yer
status "failed" Hatalı yol ya da izin journalctl -u panel -n 50
Sürekli yeniden başlıyor Uygulama başlangıçta çöküyor Günlükteki Python hatası
Çalışıyor ama sayfa açılmıyor Yanlış port ya da adres ss -ltnp
Ortam değişkeni görünmüyor EnvironmentFile yolu yanlış systemctl show panel -p Environment
Açılışta veritabanı bağlanamıyor Sıralama eksik After=mariadb.service
Değişiklik etkisiz daemon-reload unutuldu systemctl daemon-reload

Günlükleri okuma ayrıntıları için journalctl rehberi, servisin durduğunu otomatik fark etmek için servis izleme ve alarm yazısına bakın.

Sık sorulan sorular

gunicorn yerine başka bir sunucu kullanabilir miyim? Evet; uwsgi ya da waitress da olur. Önemli olan Flask'in geliştirme sunucusunu üretimde kullanmamaktır.

Kaç işçi (worker) kullanmalıyım? Küçük bir Pi'de 2 ile başlayın; bellek ve CPU kullanımını izleyip ayarlayın.

Servis neden elektrik gelince kendiliğinden başlamıyor? systemctl enable yapılmamıştır; is-enabled ile kontrol edin.

Birden fazla uygulamayı aynı Pi'de çalıştırabilir miyim? Evet, her biri için ayrı birim, ayrı port ve ayrı kullanıcı kullanın.

Not: Komutları üretim ortamında çalıştırmadan önce yedek alın ve önce test ortamında deneyin. Bir adım sizde farklı davranıyorsa bize yazın, rehberi güncelleyelim.