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
.envdosyasında tutun. Baştaki-işareti, dosya yoksa hata vermemesini sağlar. Dosyanın izninichmod 600 .envile kısıtlayın. - Restart=on-failure: Süreç beklenmedik şekilde ölürse 5 saniye sonra yeniden başlatır.
systemctl stopile 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.