Η εποπτεία των υπηρεσιών, είναι είδος διοίκησης των υπηρεσιών του λειτουργικού συστήματος, βάση της οποίας δημιουργείται μία κύρια (master) διεργασία, η οποία είναι ο κηδεμόνας των διεργασιών που πραγματοποιούν οι υπηρεσίες, και η οποία παραμένει να εκτελείται συνεχώς.
Τα οφέλη σε σύγκριση με τον παραδοσιακό τρόπο εκτέλεσης και μηχανισμών εκκίνησης, όπως για παράδειγμα το System V init, περιλαμβάνουν:
- Την ικανότητα επανεκκίνησης υπηρεσιών που έχουν αποτύχει για κάποιο λόγο
- Το γεγονός ότι δεν απαιτείται η χρήση των αρχείων pid (Process ID files)
- Ξεκάθαρη κατάσταση εργασιών
- Αξιόπιστη καταγραφή (logging), διότι η κύρια διεργασία μπορεί και καταγράφει τα stdout/stderr της υπηρεσίας και να δημιουργηθεί εγγραφή
- Γρηγορότερη ταυτόχρονη (concurrent) εκτέλεση και ικανότητα εκκίνησης και παύσης των υπηρεσιών
Υλοποίηση:
- daemontools
- daemontools-encore
- Eye: Μια υλοποίηση με Ruby
- Finit: Γρήγορο, επεκτάσιμο σύστημα εκκίνησης για Linux
- God: Μια υλοποίηση με Ruby
- immortal: Μια υλοποίηση με Go
- PM2: Διαχειριστής Διεργασιών για Node.js
- Initng
- launchd
- minit: Ένα μικτό, αλλα πλήρες σε δυνατότητες, ολοκληρωμένο σύστημα εκκίνησης για Linux
- Monit
- OpenRC: Σε πειραματικό στάδιο εξέλιξης
- runit
- Supervisor: Μια υλοποίηση με Python
- s6: Low-level διεργασία και εποπτεία διεργασιών
- Systemd
Runit σύστημα εκκίνησης, οφέλη και χαρακτηριστικά
Τα επιπλέον οφέλη του Runit έναντι των υπόλοιπων συστημάτων εκκίνησης, είναι:
- Η φορητότητα
- Η ευκολία στο πακετάρισμα
- Μικρό μέγεθος κώδικα
Ιστορία
Δυστυχώς δεν υπάρχουν επίσημα καταγεγραμμένα στοιχεία, για αυτό παραθέτω αυτά τα οποία, γνωρίζω:
Το Runit αρχικά σχεδιάστηκε και αναπτύχθηκε από τον Gerrit Pape και υποστηρίζεται από τους Denys Vlasenco (BusyBox maintainer) & Dmitry Bogatov (Debian maintainer).
Η πρώτη ιστορικά διανομή που το χρησιμοποίησε ήταν η Annvix, αλλά η διανομή που το αξιοποίησε, επί της ουσίας και διαδόθηκε, είναι το Void Linux.
Πλέον διατίθεται και στα Artix Linux, Project Trident, Antix και στο Dragora Linux που είναι και το μοναδικό Libre Linux (εγκεκριμένο από το FSF) της παρέας.
Υπάρχουν και άλλες διανομές που το αξιοποιούν, οι οποίες όμως ακόμη δεν το έχουν ανακοινώσει επίσημα.
Σχεδιασμός
Το Runit εστιάζει στον μικρού μεγέθους, αρθρωτό και φορητό πηγαίο κώδικα.
Το Runit διαιρείται σε τρία στάδια:
- Αρχικοποίηση συστήματος
- Εποπτεία υπηρεσιών
- Σταμάτημα ή επανεκκίνηση συστήματος
Σε αντίθεση με το πρώτο και το τρίτο στάδιο που πρέπει να γίνει η προσαρμογή τους στο συγκεκριμένο λειτουργικό σύστημα στο οποίο εκτελούνται, το δεύτερο στάδιο είναι φορητό ανάμεσα σε όλα τα λειτουργικά συστήματα POSIX.
Τα τρία στάδια μπορούν να διαμορφώνοντας τρία 3 εκτελέσιμα αρχεία δέσμης εντολών, τα οποία ονομάζονται 1, 2 & 3 αντίστοιχα. (το ctrlaltdel είναι ένα πρόσθετο στάδιο υπηρεσίας)
Στάδιο 1:
Το runit εκτελεί το αρχείο /
etc/runit/1 και το περιμένει να τελειώσει.
Το πρώτο στάδιο εκκίνησης του συστήματος, γίνεται σε αυτό το στάδιο.
Το /etc/runit/1 έχει τον πλήρη έλεγχο του /dev/console και παρέχει έτσι την δυνατότητα εκκίνησης τερματικού ανάγκης σε περίπτωση που η αρχική εκτέλεση οριστικοποίησης του συστήματος, αποτύχει.
Οι υπηρεσίες σε αυτό το στάδιο, δεν ξεκινούν ταυτόχρονα, λόγω εξαρτήσεων.
Στάδιο 2:
Το runit ξεκινά την εκτέλεση του αρχείου
/etc/runit/2, το οποίο δεν επιστρέφει καμία τιμή (ολοκληρώνει την λειτουργία του), εκτός αν γίνει τερματισμός ή επανεκκίνηση του λειτουργικού συστήματος.
Εάν αποτύχει να εκτελεστεί (crash), θα επανεκκινήσει αυτόματα.
Κανονικά το αρχείο
/etc/runit/2, εκτελεί το αρχείο
runsvdir.
Στο στάδιο 2, η runit προαιρετικά, διαχειρίζεται το σήμα διακοπής INT (αίτημα πληκτρολογίου ctrl-alt-del).
Στάδιο 3:
Όταν η runit λάβει εντολή παύσης ή επανεκκίνησης, ή όταν το στάδιο 2 επιστρέψει χωρίς σφάλματα (ολοκληρωθεί η λειτουργία του), τότε αναλαμβάνει τον τερματισμό του Σταδίου 2, εάν ακόμα αυτό εκτελείται και εκτελεί το αρχείο
/etc/runit/3.
Οι εργασίες του συστήματος, όπως ο τερματισμός (shutdown), η παύση (halt) και η επανεκκίνηση (reboot) εκτελούνται σε αυτό το στάδιο.
ΑξιοποίησηΤο Runit μπορεί να αντικαταστήσει το σύστημα εκκίνησης του λειτουργικού, ή μπορεί να χρησιμοποιηθεί σαν επόπτης υπηρεσιών άλλου συστήματος εκκίνησης (init) αρκεί να είναι η διεργασία-κηδεμόνας στο PID1 και η οποία εκτελεί τις διεργασίες που καθορίζονται από στο αρχείο inittab.
Τρόπος χρήσης
Το Runit διαφοροποιείται ανάμεσα στο Void και Artix, σε ότι αφορά την οργάνωση των φακέλων (τοποθεσία), των υπηρεσιών (για λοιπές λεπτομέρειες, στα Wikis των διανομών κάτω στις πηγές).
Ο τρόπος χρήσης όμως, είναι κοινός, ως εντολές.
Στην περίπτωση του άρθρου αυτού, ο συγγραφέας χρησιμοποιεί το Artix Linux, οπότε λάβετε υπόψη σας, την διαφοροποίηση του
/var/service/* (Void Linux) έναντι του
/run/runit/service/* (Artix Linux)
<service_name> είναι το όνομα της υπηρεσίας, π.χ.
clamdΠρόσθεση/αφαίρεση υπηρεσίας:
Για Artix Linux
# ln -s /etc/runit/sv/<service_name> /run/runit/service
# unlink /run/runit/service/<service_name>
Για Void Linux
# ln -s /etc/sv/<service_name> /var/service/
# rm /var/service/<service_name>
Εκκίνηση/επανεκκίνηση υπηρεσίας (Artix Linux/Void Linux):
# sv up <service_name>
# sv restart <service_name>
Τερματισμός υπηρεσίας (Artix Linux/Void Linux):
Κατάλογος ενεργών υπηρεσιών:
Για Artix Linux
# sv status /run/runit/service/*
Για Void Linux
# sv status /var/service/*
Επίπεδα υπηρεσιών (Runlevels)
Εξ' ορισμού στο Runit υπάρχουν δύο επίπεδα υπηρεσιών, το
default και το
singleΗ εναλλαγή των επιπέδων η οποία τα έχει ως αποτέλεσμα τον τερματισμό των υπηρεσιών του τρέχοντος επιπέδου και την εκκίνηση του άλλου, γίνεται με την εντολή
Υπάρχει η δυνατότητα πρόσθεσης επιπέδων υπηρεσιών, όπως και στο OpenRC με την αντίστοιχη λογική, δηλαδή ποιες υπηρεσίες θα ενεργοποιούνται σε εκείνο το επίπεδο.
Για να γίνει αυτό, δημιουργούμε ένα νέο επίπεδο υπηρεσιών, με την εντολή
# runsvchdir <runlevel_name>
Κατά συνέπεια οι παραπάνω εντολές πρόσθεσης υπηρεσίας (linked), για παράδειγμα στο Artix Linux θα είναι
# ln -s /etc/runit/sv/service /etc/runit/runsvdir/<runlevel_name>@@
(δείτε και στα Wikis του Runit & των διανομών, για περισσότερη σχετική πληροφόρηση)
Παραδείγματα γραφής υπηρεσιών για Runit
Οι υπηρεσίες στο Runit, έχουν σαν χαρακτηριστικό γνώρισμα το όνομα του εκτελέσιμου αρχείου
run, το οποίο δημιουργείται μέσα σε ένα φάκελο με την ονομασία του service
Clamd υπηρεσία για Runit
#!/bin/sh
exec clamd --foreground=true
Teamviewer υπηρεσία για Runit
#!/bin/sh
exec teamviewer daemon enable
Είναι όλα τόσο απλά στο Runit?
Η απάντηση είναι ότι είναι σαφέστατα πιο εύκολη η δημιουργία υπηρεσιών στο Runit, σε σχέση με το OpenRC, το s6 και άλλα init systems.
Ας δούμε ένα μεγαλύτερο παράδειγμα όμως, δηλαδή πως μπορεί να παραμετροποιηθεί μία υπηρεσία, όπως το zramen (δεν θα επεξηγηθεί το αρχείο δέσμης εντολήν του ιδίου, αλλά μόνο της υπηρεσίας, θα μπει όμως ως παράθεση, για μελέτη).
Το εκτελέσιμο αρχείο δέσμης
/usr/bin/zramen #!/bin/bash
# ----------------------------------------------------------------------------
# zramen: manage zram swap space
# ----------------------------------------------------------------------------
# ==============================================================================
# constants {{{
# use this compression algorithm for zram by default
readonly COMP_ALGORITHM='lz4'
readonly ZRAM_COMP_ALGORITHM="${ZRAM_COMP_ALGORITHM:-$COMP_ALGORITHM}"
# give zram swap device highest priority
readonly PRIORITY=32767
readonly ZRAM_PRIORITY="${ZRAM_PRIORITY:-$PRIORITY}"
# allocate this percentage of memory for zram by default
readonly SIZE=25
readonly ZRAM_SIZE="${ZRAM_SIZE:-$SIZE}"
# set the maximum number of compression streams for zram
readonly STREAMS=$(nproc)
readonly ZRAM_STREAMS="${ZRAM_STREAMS:-$STREAMS}"
# zramen version number
readonly VERSION=0.2.1
# set TMPDIR to work directory for mktemp
readonly WORK_DIR='/var/run/zramen'
TMPDIR="$WORK_DIR"
# end constants }}}
# ==============================================================================
# usage {{{
_usage() {
read -r -d '' _usage_string <<EOF
Usage:
zramen [-h|--help] <command>
zramen [-a|--algorithm <algo>]
[-n|--num <uint>]
[-s|--size <uint>]
[-p|--priority <int>]
make
zramen toss
Options:
-h, --help Show this help text
-v, --version Show program version
-a, --algorithm Compression algorithm for zram (Default: $ZRAM_COMP_ALGORITHM)
-n, --num Number of compression streams for zram (Default: $ZRAM_STREAMS)
-p, --priority Priority of zram swap device (Default: $ZRAM_PRIORITY)
-s, --size Percentage of memory to allocate for zram (Default: $ZRAM_SIZE)
Commands:
make Make zram swap device
toss Remove zram swap device
Algorithm
Run zramctl --help to see a list of acceptable algorithms:
| lzo
| lzo-rle
| lz4
| lz4hc
| zstd
| deflate
| 842
Num
Number of zram compression streams; try one per core
Priority
Must be an integer <= 32767; higher number means higher zram priority
Size
Percentage of memory to allocate for zram; try <= 50
EOF
echo "$_usage_string"
}
_POSITIONAL=()
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
_usage
exit 0
;;
-v|--version)
echo "$VERSION"
exit 0
;;
-a|--algorithm)
_algorithm="$2"
shift
shift
;;
-n|--num)
_streams="$2"
# shift past argument and value
shift
shift
;;
-p|--priority)
_priority="$2"
shift
shift
;;
-s|--size)
_size="$2"
shift
shift
;;
-*)
# unknown option
_usage
exit 1
;;
make|toss)
_POSITIONAL+=("$1")
shift
;;
*)
# unknown command
_usage
exit 1
;;
esac
done
if ! [[ "${#_POSITIONAL[@]}" == '1' ]]; then
_usage
exit 1
fi
# restore positional params
set -- "${_POSITIONAL[@]}"
# end usage }}}
# ==============================================================================
# sanitize compression algorithm input
_algorithm="${_algorithm:-$ZRAM_COMP_ALGORITHM}"
case "$_algorithm" in
# proper algo chosen, no action necessary
lzo|lzo-rle|lz4|lz4hc|zstd|deflate|842)
;;
# improper algo chosen, reset to default
*)
_algorithm="$COMP_ALGORITHM"
;;
esac
# sanitize priority input
_priority=${_priority:-$ZRAM_PRIORITY}
[[ $_priority -le 32767 ]] \
|| _priority=32767
# sanitize size input
_size=${_size:-$ZRAM_SIZE}
[[ $_size -gt 0 ]] \
|| _size=$SIZE
[[ $_size -le 100 ]] \
|| _size=100
# sanitize streams input
_streams=${_streams:-$ZRAM_STREAMS}
# number of CPUs requested must be 1+
[[ $_streams -gt 0 ]] \
|| _streams=1
# number of CPUs requested must not exceed CPUs available
[[ $_streams -le $STREAMS ]] \
|| _streams=$STREAMS
INFO() {
echo "zramen#info: $*"
}
WARN() {
echo "zramen#warn: $*"
}
ERRO() {
echo "zramen#erro: $*"
exit 1
}
calc() {
# truncate to whole number
printf '%.f' "$(bc --mathlib <<< "$@")"
}
make() {
local _mem_total
local _mem_to_alloc
local _zram_dev
_mem_total=$(grep 'MemTotal:' /proc/meminfo | awk '{print $2}')
_mem_to_alloc=$(calc "$_mem_total * 1024 * ($_size / 100)")
if ! [[ -d '/sys/module/zram' ]]; then
INFO 'Attempting to find zram module - not part of kernel'
modprobe --dry-run zram 2>/dev/null \
|| ERRO 'Sorry, could not find zram module'
# loop to handle zram initialization problems
for ((i=0; i < 10; i++)); do
[[ -d '/sys/module/zram' ]] \
&& break
modprobe zram
sleep 1
done
INFO 'zram module successfully loaded'
else
INFO 'zram module already loaded'
fi
for ((i=0; i < 10; i++)); do
INFO 'Attempting to initialize free device'
_tmp="$(mktemp)"
# return name of first free device
zramctl \
--algorithm "$_algorithm" \
--find \
--size "$_mem_to_alloc" \
--streams "$_streams" &> \
"$_tmp"
read -r _output < "$_tmp"
rm -f "$_tmp"
unset _tmp
case "$_output" in
*'failed to reset: Device or resource busy'*)
sleep 1
;;
*'zramctl: no free zram device found'*)
WARN 'zramctl could not find free device'
INFO 'Attempting zram hot add'
! [[ -f '/sys/class/zram-control/hot_add' ]] \
&& ERRO 'Sorry, this kernel does not support zram hot add'
read -r _hot_add < /sys/class/zram-control/hot_add
INFO "Hot added new zram swap device: /dev/zram$_hot_add"
;;
/dev/zram*)
[[ -b "$_output" ]] \
|| continue
_zram_dev="$_output"
break
;;
esac
done
if [[ -b "$_zram_dev" ]]; then
INFO "Successfully initialized zram swap device: $_zram_dev"
mkdir -p "$WORK_DIR/zram"
mkswap "$_zram_dev" --label "$(basename "$_zram_dev")" &> /dev/null \
&& swapon --discard --priority $_priority "$_zram_dev" \
&& ln --symbolic "$_zram_dev" "$WORK_DIR/zram/"
else
WARN 'Could not get free zram device'
fi
}
toss() {
for zram in "$WORK_DIR/zram"/*; do
! [[ -b $zram ]] \
&& continue
INFO "Removing zram swap device: /dev/$(basename "$zram")"
swapoff "$zram" \
&& zramctl --reset "$(basename "$zram")" \
&& rm "$zram" \
&& INFO "Removed zram swap device: /dev/$(basename "$zram")"
done
[[ -d "$WORK_DIR" ]] \
&& rm -rf "$WORK_DIR"
}
main() {
if ! [[ "$UID" == '0' ]]; then
echo 'Sorry, requires root privileges'
exit 1
fi
[[ -d "$WORK_DIR" ]] \
|| mkdir -p "$WORK_DIR"
if [[ "$1" == 'make' ]]; then
make
elif [[ "$1" == 'toss' ]]; then
toss
else
# unknown command
_usage
exit 1
fi
}
main "$1"
# vim: set filetype=sh foldmethod=marker foldlevel=0 nowrap:
Το αρχείο παραμετροποίησης του
zramen/conf (καθορίζεται ως αρχείο από τις παραμέτρους του zramen, και απλά εδώ μπορεί κατά βούληση να μεταβάλλει κανείς τον τρόπο συμπίεσης, μέγεθος κλπ, από τα προκαθορισμένα)
#export ZRAM_COMP_ALGORITHM='lz4'
#export ZRAM_PRIORITY=32767
#export ZRAM_SIZE=25
#export ZRAM_STREAMS=1
Το αρχείο εκκίνησης της υπηρεσίας του
zramen/run #!/bin/sh
[ -r ./conf ] && . ./conf
zramen make
exec pause
Το αρχείο τερματισμού της υπηρεσίας του
zramen/finishΟ λόγος ύπαρξης των run & finish, είναι ότι επειδή πρόκειται για υπηρεσία τύπου mount (όπως άλλωστε ξεκάθαρα φαίνεται στον πηγαίο κώδικα αλλά επειδή και από την φύση του το swap θέλει mounting/unmounting με τις εντολές swapon/swapoff και άρα είναι onetime script σε εκτέλεση) και έτσι πρέπει να στείλει το αντίστοιχο σήμα επιστροφής όταν τερματίσει, στον επόπτη εργασιών.
Άδεια Χρήσης Runit
To
runit είναι ελεύθερο λογισμικό και η άδεια χρήσης του είναι παρόμοια με την three-clause BSD.
http://smarden.org/runit/faq.html (αντίγραφο της άδειας του, υπάρχει ως συνημμένο αρχείο στο τέλος του άρθρου)
Καλό διάβασμα!