Linux Systemd Detayları ve Sunucuda Servis Yazmak

Linux sunucunuzda sıfırdan bir servis yazmak veya elinizdeki bir scripti servise çevirmek için systemd ana processini kullanacağız. Böylece systemctl ve service komutlarıyla kendi servisinizi özgürce kontrol edebilir, başlangıçta otomatik çalışan programlar hazırlayabilir ve dilediğinizde programınızı açıp kapatabilirsiniz. Bu yazımızda RHEL, Centos 7 kullanarak örnekleri anlatacağız.

Önyüklemede işlemleri yönetme ve başlatma mekanizması değiştirildi. RHEL / CentOS 6.x’e kadar, /etc/init.d/ içinde bir betik oluşturup chkconfig yardımıyla etkinleştirmiş olurdunuz, ancak RHEL / CentOS 7’de işler farklılaşıyor. Artık init.d yerine systemd ana processini kullanmamız gerekiyor ve systemd ana Linux dağıtımlarında default process manager olduğu için daha faydalı ve verimli bir kullanıma geçmiş oluyoruz.

1. Systemd Nedir?

Systemd programı, Linux ailesinde bulunur ve amacı; bilgisayardaki sistem ve servislerin çalışmasını organize etmektir. Bu yönetimi, systemctljournalctlnotifyanalyzecglscgtoploginctl ve nspawn olarak adlandırılan araçlar sayesinde gerçekleştirir.

Linux’taki her süreç şeffaf bir şekilde görünür olduğundan, systemd‘nin nerede gizlendiğini görerek başlayalım. Sistemimde ps -ef | grep systemd komutuyla aşağıdakileri alıyorum:

~$ ps -ef | grep systemd
root         1     0  0 15:29 ?        00:01:02 /lib/systemd/systemd --system --deserialize 22
message+   768     1  0 15:29 ?        00:05:46 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation --syslog-only
root       805     1  0 15:29 ?        00:10:22 /lib/systemd/systemd-logind
ankush    1132     1  0 15:29 ?        00:00:00 /lib/systemd/systemd --user
ankush    1176  1132  0 15:29 ?        00:04:50 /usr/bin/dbus-daemon --session --address=systemd: --nofork --nopidfile --systemd-activation --syslog-only
ankush    9772 20029  0 21:11 pts/6    00:00:00 grep --color=auto systemd
systemd+ 17298     1  0 15:29 ?        00:00:12 /lib/systemd/systemd-resolved
systemd+ 17303     1  0 15:29 ?        00:00:00 /lib/systemd/systemd-timesyncd
root     17307     1  0 15:29 ?        00:00:02 /lib/systemd/systemd-journald
root     18182     1  0 15:29 ?        00:00:00 /lib/systemd/systemd-udevd

Sizinde farkettiğinizi gibi listedeki ilk işlem root olarak başlatıldı ve pid 1’e sahip. Bu, sistemin önyükleme (boot) sırasında başlattığı ilk işlemdi. Systemd‘ye merhaba deyin 🙂

Dolayısıyla, oldukça basit bir şekilde systemd, sistemdeki diğer işlemleri başlatan, yöneten ve sonlandıran, günlükler, dosya sistemi durumları vb. hakkında bilgi sağlamanın yanı sıra özetle “ana” işlemdir.

Systemd binary’si /usr/lib/systemd/systemd dosyası olarak sistemde bulunmakta ve systemd rpm paketi tarafından kurulmaktadır. Systemd binary’si eski yapıdaki /sbin/init programının yerini alır. Şu an için RHEL 7 ‘de zaten /sbin/init tam manasıyla /usr/lib/systemd/systemd dosyasına bir linktir.

1.1 Systemd Önemli Dizinler

Systemd ile çalışırken aşağıdaki dizin adreslerini bilmek işimizi kolaylaştıracaktır.

DizinAçıklama
/usr/lib/systemd/system/RPM paketleri systemd servislerinin yapılandırma dosyasını buraya kurmaktadır.
/run/systemd/system/Runtime dediğimiz işletim sistemi çalışırken bu dizin kullanılır.
/etc/systemd/system/Systemd servisleri enable veya disable edildiğinde /usr/lib/systemd/system dizinindeki dosyalar buraya linklenir.

1.2 Unit

Systemd, unit adı verdiğimiz bileşen ve bunların kendi arasındaki ilişkilerinden oluşmaktadır. Bu yapılandırma dosyaları sadece servisler için değil runlevel’ların yerini alan target’lar, mount edilecek bileşenleri içeren kısımlar ve birçok bileşenden oluşabilmektedir. Bu dosyalar /usr/lib/systemd/system dizini altında şu uzantılarla yer alabilmektedir.

Unit TipiDosya UzantısıAçıklaması
Service unit.serviceSistem servisi
Target unit.targetSystemd unit grubu (eski runlevel’lar)
Automount unit.automountAutomount edilecekler
Device unit.deviceKernel’ın tanıdığı cihaz dosyaları.
Mount unit.mountMount edilecek dosya sistemleri
Path unit.pathDosya sistemindeki dosya veya dizinler
Scope unit.scopeSonradan çalıştırılan harici programlar.
Slice unit.sliceSistem süreçlerini yöneten belirli bir hiyerarşideki unit dosyaları.
Snapshot unit.snapshotSystemd’nin snapshotları (kendimizin oluşturuduğu runlevel’lar denebilir)
Socket unit.socketIPC için kullanılan dosyaları oluşturan unit dosyaları
Swap unit.swapSwap dosyası veya swapleri aktive eden unit dosysaları
Timer unit.timerSystemd timer

1.3 Target

Systemd ile birlikte anmamız ve es geçmemiz bir konuda Target‘lardır. Yani eski isimleriyle runlevel‘lar. Sunucularımızın, sunduğu hizmetleri yerine getirilebilmesi için, bu hizmetlerin sunucu önyükleme işlemi sırasında çağırılması ve farklı seviyelere ulaşması gerekir. Bazı hizmetler, bilgisayar örneğin kurtarma düzeyine (run level 0) ulaştığında ve bazıları da multi-user (çok kullanıcı) düzeyine (run level 3) ulaştığında çalıştırılmak üzere çağırılır. Bu seviyeleri, targets (hedefler) olarak düşünebilirsiniz.

Farklı servislerin birleşmesi ile oluşan runlevel’lara artık target ismi verilmektedir. Bu runlevel’ların geriye dönük isimlendirilmeler korunsa da uzun vadede bu takma adlar (aliaslar) kaldırılacaktır. Eskiden 0-6 arası runlevel’lar varken yeni yapıdaki target’lar şu şekildedir.

RunlevelTarget UnitAçıklama
0runlevel0.target, poweroff.targetPoweroff target
1runlevel1.target, rescue.targetRescue yani kurtarma target’ı
2runlevel2.target, multi-user.targetÇoklu kullanıcı target’ı (runlevel 2,runlevel 3 ve 4 aynı)
3runlevel3.target, multi-user.targetÇoklu kullanıcı target’ı (runlevel 2,runlevel 3 ve 4 aynı)
4runlevel4.target, multi-user.targetÇoklu kullanıcı target’ı (runlevel 2,runlevel 3 ve 4 aynı)
5runlevel5.target, graphical.targetGrafik ekranın başlatıldığı target
6runlevel6.target, reboot.targetReboot target

Sisteminizin o an hangi target ile çalıştığını görmek için systemctl get-default komutunu kullanabilirsiniz.

2. Neden RHEL Systemd Kullanmaya Başladı?

Yukarıdaki başlıkta gördüğünüz gibi, systemd bir sistem ve süreç yöneticisidir ve RHEL 7’de UpStart (init.d) programının yerini almaktadır. RHEL bu kararı neden aldı? Bunun için çok iyi nedenler var, o yüzden hızlıca bir göz atalım.

2.1 Paralel hizmet başlatma

SysV init programından farklı olarak systemd hizmetleri paralel olarak başlatma yeteneğine sahiptir. Init programını hizmetleri birer birer başlatırken systemd onları eş zamanlı olarak başlatır. Mobil cihazların bile çok çekirdekli CPU’lara sahip olduğu bir çağda, paralel başlatmanın olmaması init.d açısından büyük bir sorun.

2.2 Dinamik (hot) servis yönetimi

USB sürücülerin Ubuntu ve benzeri dağıtımlarda otomatik olarak açılırken önceki Fedora sistemlerinde mount edilmesi gerektiğini fark ettiyseniz, bunun nedeni systemd’dir. Donanımdaki canlı değişiklikleri algılayabilir ve sonlandırabilir.

2.3 Ertelenmiş hizmet başlatma

Systemd, hizmetin başlamasını gerçekten ihtiyaç duyulduğu zamana erteleyebildiği için önyükleme sürelerini kısaltır. Basit bir örnek olarak ağ dosya sistemi ile ilgili hizmetleri verebiliriz. Kullanılabilir ağa bağlı disk yoksa, çalışır durumda bir hizmetin olması mantıklı değildir.

2.4 Daha hızlı süreç iletişimi

Systemd’nin paralel yetenekleri süreçler arası iletişime geçer. Systemd, soketlere ve sistem veriyoluna paralel erişim sunarak iletişim kaynakları için işlem bekleme sürelerini önemli ölçüde azaltır.

2.5 Otomatik yeniden başlatma

Bir hizmet çökerse, systemd bunu algılayabilir ve yeniden başlatmayı deneyebilir. Çoğu zaman, daha temel sorunlar olmadıkça, bir uygulamanın yeniden çalışmaya başlaması için gereken tek şey basit bir yeniden başlatma işlemidir.

Kısa bir özetle daha kıyaslama yapacak olursak;

  1. SysV init script’leri ile tam uyumludur. Init.d altındaki scriptleri de çalıştırabilir.
  2. Servislerin parallel olarak başlatılabilmesi (dolayısıyla daha hızlı sistem açılışı ve kapanışı)
  3. Servislerin on-deamon aktivasyonu. Yani bir servisi başlatırken onun ihtiyacı olan diğer servislerin de otomatik başlatılması
  4. Sistem servislerinin snapshot’ının alınabilmesi (Yani basitçe ifade ile kendi runlevel’larınızı oluşturabilme diyebiliriz buna)
  5. Servis bir şekilde ölürse bunun tesbiti ve ilgili servisin tekrar başlatılması

3. Servis Nasıl Yazılır?

Systemd hakkında detaylı iki başlıktan sonra artık servisimizi yazmak için kolları sıvayalım. Bunun için ilk olarak root kullanıcısına geçiniz ve /etc/systemd/system/ dizinine giderek servis adımızla yeni bir dosyayı nano veya vim sayesinde oluşturarak açanız. Ardından aşağıdaki örnek şablon üzerinden kullanılabilecek parametrelerin neler oldukları konusunda detay verelim.

[Unit]
Description=Live
After=network.target
Requires=syslog.target

[Install]
WantedBy=multi-user.target
Alias=hello_kerteriz.service

[Service]
Type=simple
User=root
Group=wheel
Restart=always
RestartSec=1
ExecStart=/usr/bin/sh /home/kerteriz/sayhello.sh
ExecStop=/bin/kill  ${MAINPID}

# Execute pre and post scripts as root
PermissionsStartOnly=true
  • Description: Servis açıklaması
  • After: Hangi servisten sonra çalışması gerektiğini söyler. Bu örnekte network servisi başlatıldıktan sonra servisimiz başlar.
  • Requires: Servisimizin çalışabilmesi için hangi servisin çalışıyor olması gerektiğini belirleyebiliriz.
  • WantedBy: Servisimizin hangi seviyede çalışacağını gösterir. Dolayısıyla, hizmetimizi etkinleştirirsek, multi-user.target.wants klasöründe, bu hizmete sembolik bir bağlantı oluşturulacaktır. Disable edildiğinde de silinir.
  • Alias: Servisin ismi
  • Type: simple ile herhangi bir zamanda yalnızca bir instance çalışacaktır.
  • User=root: Hangi kullanıcıda çalışacağı
  • Group=wheel: Hangi grupta çalışacağı
  • Restart: Servis bir şekilde kapanırsa ne zaman restart edileceği. Biz her clean veya unclean signal bulunduğunda restart etmesi için (always) kullanıyoruz. Tabi bunun yanında; unclean exit code veya signal bulunduğunda restart eden on-failure, unclean signal, watchdog veya timeout olduğunda restart eden on-abnormal ve yalnızca clean bir signal veya return code ile durdurulduğunda restart eden on-success parametreleri de mevcuttur.
  • RestartSec: Eğer yazılmazsa, sistem default 100ms sonra yeniden başlatmayı dener, biz 1 saniye sonra başlat dedik
  • ExecStart: Çalıştırmak istediğimiz binary (sh) ve dosya adı (sayhello.sh)
  • ExecStop: Servisi stop ettiğimizde çalışacak komut. Burada kill ediyoruz.
  • StartLimitIntervalSec=10: Servis kapanırsa, ve yeniden başlattığında başarılı olamıyorsa, systemde 10 saniyelik bir süre ile 5 defa yeniden dener, başlatamazsa bir daha denemez.

Bu bilgiler ışığında oldukça basit ve minimum gereksinimlerle bir service dosyası oluşturalım:

cd /etc/systemd/system/
vim hello_kerteriz.service
[Unit]
Description=Say Hello Program
After=network.target

[Service]
Type=simple
User=root
Restart=always
ExecStart=/usr/bin/sh /home/kerteriz/sayhello.sh
ExecStop=/bin/kill  ${MAINPID}

[Install]
WantedBy=multi-user.target

Ardından servis daemonunu yeniden başlatarak yaptığımız değişikliği algılamasını sağlayalım.

systemctl daemon-reload

Şimdi ise hem servisimizin başlangıçta çalışması (enable) hemde şu anda çalışmaya başlaması (start) için aşağıdaki komutla servisimizi başlatalım.

systemctl enable --now hello_kerteriz

Servisimizin çalışıp çalışmadığını görmek için status çıktısına bakabiliriz.

systemctl status hello_kerteriz

Yazdığım basit /home/kerteriz/sayhello.sh dosyasının içeriği şu şekildeydi ve 3 sn de bir çıktı dosyasına belirttiğim yazıyı yazıyordu;

echo "Merhaba Kerteriz Blog" >> /home/dataserver/say_out.txt
sleep 3

Bu shell komutlarınında çalışıp çalışmadığına bakalım;

Böylece yazdığımız scripti, programı vs. servise çevirerek tam kontrolü ele almış olduk. Takıldığınız yerleri sormak için aşağıdaki yorum kutusunu kullanabilirsiniz.