-
-
Notifications
You must be signed in to change notification settings - Fork 14
/
2018-systemd-golang-socket-activation.html
600 lines (505 loc) · 23.5 KB
/
2018-systemd-golang-socket-activation.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
---
title: "Intégration d'un service en Go avec systemd: activation par socket"
description: |
En utilisant l'activation par socket de systemd, il est possible d'effectuer
des déploiements sans impact d'un service Go en quelques lignes de code.
uuid: 5463b0ca-6eb9-48e8-a36e-ed8a829fdb8d
tags:
- programming-go
---
Dans un [article précédent][previous post], j'ai souligné certaines
fonctionnalités utiles de *systemd* pour écrire un service en Go,
notamment pour indiquer la *disponibilité* et prouver la
*vivacité*. Un autre point intéressant est l'**activation par
socket**[^traduction] : *systemd* écoute pour le compte de
l'application et, lors de la première requête, démarre le service avec
une copie de la socket en écoute. Lennart Poettering [détaille dans un
article][details it in a blog post] :
> Si un service meurt, sa socket d'écoute reste en place, sans perdre
> un seul message. Après un redémarrage du service suite à une panne,
> il peut continuer là où il s'est arrêté. Si un service est mis à
> niveau, nous pouvons redémarrer le service tout en conservant ses
> sockets, assurant ainsi que le service est continuellement
> disponible. Aucune connexion n'est perdue pendant la mise à niveau.
[^traduction]: La traduction de *socket* en français n'est pas
évidente. Quand le contexte est suffisamment clair, je m'amuse
parfois à dire « chaussette » 🧦 car "socket" est souvent écrit
`sock` dans les programmes. On pourrait le traduire par « prise
réseau », idéal pour rendre perplexe la plupart des lecteurs.
Il s'agit d'une solution pour obtenir un **déploiement sans impact**
d'une application. Un autre avantage est la possibilité d'exécuter un
démon avec moins de privilèges : perdre des droits est une tâche ardue
avec Go[^system].
[^system]: De nombreuses caractéristiques d'un processus sous Linux
sont attachées aux fils d'exécution. L'environnement d'exécution
de Go les gère de manière transparente pour l'utilisateur. Jusqu'à
[récemment][recently], cela rendait certaines fonctionnalités,
comme `setuid()` ou `setns()`, inutilisables.
[TOC]
# La base
Reprenons notre sympathique serveur de pages 404 :
::go
package main
import (
"log"
"net"
"net/http"
)
func main() {
listener, err := net.Listen("tcp", ":8081")
if err != nil {
log.Panicf("cannot listen: %s", err)
}
http.Serve(listener, nil)
}
Voici la version utilisant l'activation par socket, à l'aide de
[go-systemd][] :
::go hl_lines="11 19"
package main
import (
"log"
"net/http"
"github.com/coreos/go-systemd/activation"
)
func main() {
listeners, err := activation.Listeners(true) // ❶
if err != nil {
log.Panicf("cannot retrieve listeners: %s", err)
}
if len(listeners) != 1 {
log.Panicf("unexpected number of socket activation (%d != 1)",
len(listeners))
}
http.Serve(listeners[0], nil) // ❷
}
En ❶, nous récupérons les sockets en écoute fournies par *systemd*. En
❷, nous utilisons la première d'entre elles pour servir les requêtes
HTTP. Testons le résultat avec `systemd-socket-activate`[^old] :
::console
$ go build 404.go
$ systemd-socket-activate -l 8000 ./404
Listening on [::]:8000 as 3.
[^old]: Avec d'anciennes versions de *systemd* (avant 230), la
commande peut s'appeler `/lib/systemd/systemd-activate`.
Dans un autre terminal, effectuons quelques requêtes :
::console
$ curl '[::1]':8000
404 page not found
$ curl '[::1]':8000
404 page not found
Deux fichiers sont nécessaires pour compléter l'intégration avec
*systemd* :
- un fichier `.socket` décrivant la socket,
- un fichier `.service` décrivant le service associé.
Voici le contenu du fichier `404.socket` :
::systemd
[Socket]
ListenStream = 8000
BindIPv6Only = both
[Install]
WantedBy = sockets.target
La page de manuel [`systemd.socket(5)`][systemd.socket] décrit les
options disponibles. `BindIPv6Only = both` est explicitement utilisé
car sa valeur par défaut dépend de la distribution. Voici ensuite le
contenu du fichier `404.service` :
::systemd
[Unit]
Description = 404 micro-service
[Service]
ExecStart = /usr/bin/404
*systemd* sait que les deux fichiers sont liés car ils partagent un
même préfixe. Placez les dans `/etc/systemd/system` et exécutez
`systemctl daemon-reload` et `systemctl start 404.socket`. Votre
service est désormais prêt à accepter des connexions !
# Gestion des connexions existantes
Notre serveur de pages 404 a un défaut majeur : les connexions
existantes sont sauvagement tuées lorsque le démon est arrêté ou
redémarré. Corrigeons cela !
## Attendre quelques secondes les connexions existantes
Nous pouvons inclure une courte période de tolérance pour terminer les
connexions en cours. À l'issue de celles-ci, les connexions restantes
sont tuées :
::go hl_lines="15 22"
// À la réception du signal, ferme en douceur le serveur et
// attend 5 secondes la fin des connexions en cours.
done := make(chan struct{})
quit := make(chan os.Signal, 1)
server := &http.Server{}
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-quit
log.Println("server is shutting down")
ctx, cancel := context.WithTimeout(context.Background(),
5*time.Second)
defer cancel()
server.SetKeepAlivesEnabled(false)
if err := server.Shutdown(ctx); err != nil {
log.Panicf("cannot gracefully shut down the server: %s", err)
}
close(done)
}()
// Accepte de nouvelles connexions.
server.Serve(listeners[0])
// Attend la fin des connexions en cours avant de sortir.
<-done
À la réception du signal de terminaison, la goroutine reprend et
[planifie l'arrêt du service][schedule a shutdown of the service] :
> `Shutdown()` ferme en douceur le serveur sans interrompre les
> connexions actives. `Shutdown()` fonctionne en fermant d'abord les
> sockets en écoute puis en fermant toutes les connexions inactives et
> enfin en attendant indéfiniment que les connexions retombent au
> repos et s'arrêtent.
Durant le redémarrage, les nouvelles connexions ne sont pas
acceptées : elles restent dans la file d'attente associée à la
socket. La taille de celle-ci est limitée et peut être configurée avec
la directive `Backlog`. Sa valeur par défaut est 128. Vous pouvez
conserver cette valeur même si votre service s'attend à recevoir de
nombreuses connexions par seconde. Lorsque cette valeur est dépassée,
les connexions entrantes sont ignorées. Le client réessaye
automatiquement de se connecter. Sous Linux, par défaut, un client
tente 5 fois (`tcp_syn_retries`) en 3 minutes environ. C'est un bon
moyen d'éviter l'effet de troupeau qui se manifesterait au redémarrage
si la taille de la file d'attente était augmentée à une valeur plus
élevée.
## Attendre plus longtemps les connexions existantes
Si vous voulez attendre la fin des connexions existantes pendant une
longue période, vous avez besoin d'une approche alternative pour
éviter d'ignorer les nouvelles connexions pendant plusieurs
minutes. Il y a une astuce très simple : **demander à *systemd* de ne
tuer aucun processus**. Avec `KillMode = none`, seule la commande
d'arrêt est exécutée et tous les processus existants ne sont pas
perturbés :
::systemd hl_lines="6 7"
[Unit]
Description = slow 404 micro-service
[Service]
ExecStart = /usr/bin/404
ExecStop = /bin/kill $MAINPID
KillMode = none
Si vous redémarrez le service, le processus en cours prend le temps
nécessaire pour s'arrêter et *systemd* lance immédiatement une
nouvelle instance, prête à répondre aux requêtes avec sa propre copie
de la socket d'écoute. Toutefois, nous perdons la capacité d'attendre
que le service s'arrête complètement, soit par lui-même, soit de force
après un temps limite avec `SIGKILL`.
!!! "Mise à jour (01.2021)" L'utilisation de `KillMode=none` est
désormais [découragée][deprecated]. En plus de l'alternative proposée
ci-dessous, il est possible de transmettre les connexions actives à
*systemd* à l'aide de la fonction
[`sd_pid_notify_with_fds()`][passfd]. Ensuite, le nouveau processus
doit être capable de les gérer, ce qui n'est pas facile car cela
nécessite de fournir également l'état associé à chaque connexion. De
plus, cette fonction n'est [pas encore présente][not implemented] dans
*go-systemd*.
## Attendre plus longtemps les connexions existantes (alternative)
Une alternative à la solution précédente consiste à **faire croire à
*systemd* que le service est mort**️ pendant le redémarrage.
::go hl_lines="26 27"
done := make(chan struct{})
quit := make(chan os.Signal, 1)
server := &http.Server{}
signal.Notify(quit,
// redémarrage:
syscall.SIGHUP,
// arrêt:
syscall.SIGINT, syscall.SIGTERM)
go func() {
sig := <-quit
switch sig {
case syscall.SIGINT, syscall.SIGTERM:
// Arrêt avec limite de temps.
log.Println("server is shutting down")
ctx, cancel := context.WithTimeout(context.Background(),
15*time.Second)
defer cancel()
server.SetKeepAlivesEnabled(false)
if err := server.Shutdown(ctx); err != nil {
log.Panicf("cannot gracefully shut down the server: %s", err)
}
case syscall.SIGHUP: // ❶
// Exécute un processus éphémère et demande à systemd de
// le suivre au lieu de nous.
log.Println("server is reloading")
pid := detachedSleep()
daemon.SdNotify(false, fmt.Sprintf("MAINPID=%d", pid))
time.Sleep(time.Second)
// Attend sans limite de temps la fin des connexions en cours.
server.SetKeepAlivesEnabled(false)
if err := server.Shutdown(context.Background()); err != nil {
log.Panicf("cannot gracefully shut down the server: %s", err)
}
}
close(done)
}()
// Sert lentement les requêtes.
server.Handler = http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
time.Sleep(10 * time.Second)
http.Error(w, "404 not found", http.StatusNotFound)
})
server.Serve(listeners[0])
// Attend que toutes les connexions se terminent.
<-done
log.Println("server terminated")
La principale différence est le traitement du signal `SIGHUP` en ❶ :
un processus leurre de courte durée est exécuté et *systemd* est
invité à le suivre. Quand il meurt, *systemd* lancera une nouvelle
instance du service. Cette méthode nécessite quelques bricolages :
*systemd* a besoin que le leurre soit son fils mais Go [ne peut pas
facilement se mettre en arrière plan][cannot daemonize] seul. Par
conséquent, nous utilisons un court script Python inclu dans la
fonction `detachedSleep()`[^python] :
::go
// detachedSleep exécute un processus dormant une seconde
// en arrière plan et retourne son PID.
func detachedSleep() uint64 {
py := `
import os
import time
pid = os.fork()
if pid == 0:
for fd in {0, 1, 2}:
os.close(fd)
time.sleep(1)
else:
print(pid)
`
cmd := exec.Command("/usr/bin/python3", "-c", py)
out, err := cmd.Output()
if err != nil {
log.Panicf("cannot execute sleep command: %s", err)
}
pid, err := strconv.ParseUint(strings.TrimSpace(string(out)), 10, 64)
if err != nil {
log.Panicf("cannot parse PID of sleep command: %s", err)
}
return pid
}
[^python]: Python est un bon candidat : il est sans doute disponible
sur le système, il est d'assez bas niveau pour implémenter
facilement la fonctionnalité et, en tant que langage interprété,
il ne nécessite pas d'étape de compilation.
Il n'y a pas besoin d'appeler `fork()`
deux fois car il faut uniquement détacher le leurre du processus
courant. Cela simplifie sensiblement le code Python.
Pendant le rechargement, il peut y avoir une courte période pendant
laquelle le nouveau et l'ancien processus acceptent les requêtes
entrantes. Si vous ne le souhaitez pas, vous pouvez déplacer la
création du processus leurre en dehors de la goroutine, après
`server.Serve()` ou implémenter un mécanisme de synchronisation. Il y
a aussi un éventuel problème de concurrence lorsque nous disons à
*systemd* de suivre un autre PID (voir [PR #7816][]).
Le fichier `404.service` doit être mis à jour :
::systemd
[Unit]
Description = slow 404 micro-service
[Service]
ExecStart = /usr/bin/404
ExecReload = /bin/kill -HUP $MAINPID
Restart = always
NotifyAccess = main
KillMode = process
Chacune des directives supplémentaires a son importance :
- `ExecReload` indique comment recharger le processus (avec `SIGHUP`).
- `Restart` indique de redémarrer le processus s'il s'arrête de
manière « inattendue », notamment lors du rechargement[^socket].
- `NotifyAccess` précise quels sont les processus autorisés à envoyer
des notifications comme le changement de PID.
- `KillMode` indique de ne tuer que le processus identifié comme
principal. Les autres sont laissés tranquilles.
[^socket]: Cette directive n'est pas essentielle car le processus
serait aussi redémarré via l'activation de la socket.
## Déploiement sans impact ?
Le déploiement sans impact est une entreprise difficile sur Linux. Par
exemple, [HAProxy][] a eu une [longue][hack1] [liste][hack2] de
[tentatives][hack3] jusqu'à ce qu'une [solution appropriée, mais
complexe][proper—and complex—solution], soit implémentée dans
*HAproxy* 1.8. Comment se débrouille-t-on avec notre simple mise en
œuvre ?
Du point de vue du noyau, il y a **une seule socket** avec une **file
d'attente unique**. Cette socket est associée à plusieurs descripteurs
de fichiers : un dans *systemd* et un dans le processus en cours. La
chaussette reste en vie tant qu'il y a au moins un descripteur de
fichier. Une connexion entrante est placée par le noyau dans la file
d'attente et peut être traitée à partir de n'importe quel descripteur
avec l'appel système `accept()`. Par conséquent, cette approche permet
de réaliser un déploiement sans impact : aucune connexion entrante
n'est rejetée.
En revanche, *HAProxy* utilisait plusieurs sockets différentes pour
écouter sur les mêmes adresses, grâce à l'option
[`SO_REUSEPORT`][SO_REUSEPORT][^why]. Chaque socket a sa propre file
d'attente et le noyau répartit les connexions entrantes entre chacune
d'elles. Lorsqu'une socket se ferme, le contenu de sa file d'attente
est perdu. Si une connexion entrante se trouvait ici, elle reçoit une
réinitialisation. Une modification élégante pour Linux afin de
[signaler qu'une socket ne devrait plus recevoir de nouvelles
connexions][signal a socket should not receive new connections] a été
rejetée. *HAProxy* 1.8 recycle désormais les sockets existantes vers
les nouveaux processus par le biais d'une socket Unix.
[^why]: Cette approche est plus pratique lors d'un rechargement car il
n'y a pas à déterminer quelles sockets réutiliser et lesquelles
créer à partir de zéro. De plus, lorsque plusieurs processus ont
besoin d'accepter des connexions, l'utilisation de plusieurs
sockets est plus performante car les différents processus ne se
disputeront pas sur un verrou partagé pour accepter des
connexions.
J'espère que ce billet et le [précédent][previous post] montrent
combien *systemd* est un compagnon appréciable pour un service en Go :
*disponibilité*, *vivacité* et *activation par socket* sont quelques
unes des fonctionnalités utiles pour construire une application plus
fiable.
# Annexe: leurre écrit en Go
!!! "Mise à jour (03.2018)" Sur [/r/golang][], on m'a fait remarquer
que, dans la version où *systemd* suit un leurre, le script Python
peut être remplacé par une invocation de l'exécutable principal qui se
base sur un changement d'environnement pour prendre le rôle du
leurre. Voici le code remplaçant la fonction `detachedSleep()`
function :
::go
func init() {
// Au plus tôt, vérifie si on doit jouer le rôle
// du leurre.
state := os.Getenv("__SLEEPY")
os.Unsetenv("__SLEEPY")
switch state {
case "1":
// Première étape, se réexécuter
execPath := self()
child, err := os.StartProcess(
execPath,
[]string{execPath},
&os.ProcAttr{
Env: append(os.Environ(), "__SLEEPY=2"),
})
if err != nil {
log.Panicf("cannot execute sleep command: %s", err)
}
// Publie le PID du fils et sort.
fmt.Printf("%d", child.Pid)
os.Exit(0)
case "2":
// Dort et sort.
time.Sleep(time.Second)
os.Exit(0)
}
}
// self retourne le chemin absolu vers nous-même. Cela repose sur
// /proc/self/exe qui peut être un lien symbolique vers un fichier
// supprimé (durant une mise à jour par exemple).
func self() string {
execPath, err := os.Readlink("/proc/self/exe")
if err != nil {
log.Panicf("cannot get self path: %s", err)
}
execPath = strings.TrimSuffix(execPath, " (deleted)")
return execpath
}
// detachedSleep détache un processus qui dort une seconde et retourne
// son PID.
func detachedSleep() uint64 {
cmd := exec.Command(self())
cmd.Env = append(os.Environ(), "__SLEEPY=1")
out, err := cmd.Output()
if err != nil {
log.Panicf("cannot execute sleep command: %s", err)
}
pid, err := strconv.ParseUint(strings.TrimSpace(string(out)), 10, 64)
if err != nil {
log.Panicf("cannot parse PID of sleep command: %s", err)
}
return pid
}
# Annexe : nommage des sockets
Pour un service donné, *systemd* peut fournir plusieurs sockets. Pour
les différencier, il est possible de les nommer. Par exemple,
supposons que nous voulions aussi retourner des codes d'erreur 403
depuis le même service mais sur un port différent. Nous ajoutons une
définition de socket supplémentaire, `403.socket`, liée à la tâche
`404.service` :
::systemd hl_lines="4"
[Socket]
ListenStream = 8001
BindIPv6Only = both
Service = 404.service
[Install]
WantedBy=sockets.target
À moins de le spécifier explicitement avec la directive
`FileDescriptorName`, le nom de la socket est le nom de l'unité :
`403.socket`. *go-systemd* fournit la fonction `ListenersWithName()`
pour récupérer une correspondance entre les noms et les sockets :
::go hl_lines="24"
package main
import (
"log"
"net/http"
"sync"
"github.com/coreos/go-systemd/activation"
)
func main() {
var wg sync.WaitGroup
// Associe un nom de socket à une fonction de gestion.
handlers := map[string]http.HandlerFunc{
"404.socket": http.NotFound,
"403.socket": func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "403 forbidden",
http.StatusForbidden)
},
}
// Récupère les sockets en écoute.
listeners, err := activation.ListenersWithNames(true)
if err != nil {
log.Panicf("cannot retrieve listeners: %s", err)
}
// Pour chaque socket, invoque une goroutine en utilisant
// la fonction de gestion adéquate.
for name := range listeners {
for idx := range listeners[name] {
wg.Add(1)
go func(name string, idx int) {
defer wg.Done()
http.Serve(
listeners[name][idx],
handlers[name])
}(name, idx)
}
}
// Attend que toutes les goroutines terminent.
wg.Wait()
}
Compilons le service et lançons le via `systemd-socket-activate`:
::console
$ go build 404.go
$ systemd-socket-activate -l 8000 -l 8001 \
> --fdname=404.socket:403.socket \
> ./404
Listening on [::]:8000 as 3.
Listening on [::]:8001 as 4.
Dans une autre console, nous pouvons tester une requête sur chacune
des deux adresses :
::console
$ curl '[::1]':8000
404 page not found
$ curl '[::1]':8001
403 forbidden
[previous post]: [[fr/blog/2017-systemd-golang.html]] "Intégration d'un service en Go avec systemd: disponibilité et vivacité"
[details it in a blog post]: http://0pointer.de/blog/projects/socket-activation.html "systemd for Developers I: socket activation"
[go-systemd]: https://github.com/coreos/go-systemd/ "Go bindings for systemd"
[systemd.socket]: https://manpages.debian.org/stretch/systemd/systemd.socket.5.en.html "systemd.socket(5) manual page"
[schedule a shutdown of the service]: https://godoc.org/net/http#Server.Shutdown "godoc: net/http Server Shutdown"
[endless]: https://github.com/fvbock/endless "Zero downtime restarts for go servers"
[yet]: https://github.com/coreos/go-systemd/pull/251 "activation: add two functions to provide listeners with names"
[HAProxy]: https://www.haproxy.org/ "HAProxy: The Reliable, High Performance TCP/HTTP Load Balancer"
[hack1]: https://web.archive.org/web/2018/https://engineeringblog.yelp.com/2015/04/true-zero-downtime-haproxy-reloads.html "True Zero Downtime HAProxy Reload"
[hack2]: https://medium.com/@Drew_Stokes/actual-zero-downtime-with-haproxy-18318578fde6 "Actual Zero-Downtime with HAProxy"
[hack3]: https://githubengineering.com/glb-part-2-haproxy-zero-downtime-zero-delay-reloads-with-multibinder/ "GLB part 2: HAProxy zero-downtime, zero-delay reloads with multibinder"
[proper—and complex—solution]: https://archive.today/2018/https://www.haproxy.com/blog/truly-seamless-reloads-with-haproxy-no-more-hacks/ "Truly Seamless Reloads with HAProxy – No More Hacks!"
[SO_REUSEPORT]: https://manpages.debian.org/stretch/manpages/socket.7.en.html "socket - Linux socket interface"
[signal a socket should not receive new connections]: https://www.mail-archive.com/netdev@vger.kernel.org/msg91809.html "Re: [PATCH 1/1] net: Add SO_REUSEPORT_LISTEN_OFF socket option as drain mode"
[dup2]: https://manpages.debian.org/stretch/manpages-fr-dev/dup2.2.fr.html "dup, dup2, dup3 - Dupliquer un descripteur de fichier"
[PR #7816]: https://github.com/systemd/systemd/pull/7816 "Make MAINPID= and PIDFile= handling more restrictive"
[cannot daemonize]: https://github.com/golang/go/issues/227 "runtime: support for daemonize"
[recently]: https://golang.org/doc/go1.10#runtime "Go 1.10 Release Notes: Runtime"
[/r/golang]: https://www.reddit.com/r/golang/comments/85sm59/integration_of_a_go_service_with_systemd_socket/ "/r/golang: Integration of a Go service with systemd: socket activation"
[deprecated]: https://github.com/systemd/systemd/pull/15928 "PR #15928: warn on KillMode=none, inform about left-over processes on stop"
[passfd]: https://manpages.debian.org/buster/libelogind-dev-doc/sd_pid_notify_with_fds.3.en.html "Manual page sd_notify(3)"
[not implemented]: https://github.com/coreos/go-systemd/issues/199 "Issue #199: Missing interfaces for sd_pid_notify_with_fds and sd_listen_fds_with_names"