Nomad Logs
OpenTelemetry: logs pour Nomad en utilisant Prometheus, et Grafana

Bon maintenant on va centraliser les logs. Pour cela on va déployer Loki, Vector, et Grafana pour explorer la donnée.
Déployer Loki
On crée le fichier loki.hcl.
0job "loki" {
1 datacenters = ["services"]
2 type = "service"
3
4 group "loki" {
5 count = 1
6 network {
7 mode = "host"
8 port "loki" {
9 to = 3100
10 }
11 }
12 service {
13 name = "loki"
14 port = "loki"
15 provider = "nomad"
16 tags = [
17 "traefik.enable=true",
18 "traefik.http.routers.loki.service=loki",
19 "traefik.http.routers.loki.rule=Host(`loki.service.local`)",
20 "traefik.http.routers.loki.tls=true",
21 ]
22 }
23 task "loki" {
24 driver = "docker"
25 user = "root"
26 config {
27 image = "grafana/loki:2.9.7"
28 args = [
29 "-config.file",
30 "local/config.yml",
31 ]
32 volumes = ["/opt/data/loki:/loki"]
33 ports = ["loki"]
34 }
35 template {
36 data=<<EOH
37auth_enabled: false
38server:
39 http_listen_port: 3100
40ingester:
41 lifecycler:
42 address: 127.0.0.1
43 ring:
44 kvstore:
45 store: inmemory
46 replication_factor: 1
47 final_sleep: 0s
48 chunk_idle_period: 1h
49 max_chunk_age: 1h
50 chunk_target_size: 1048576
51 chunk_retain_period: 30s
52 max_transfer_retries: 0 # Chunk transfers disabled
53 wal:
54 enabled: true
55 dir: "/loki/wal"
56schema_config:
57 configs:
58 - from: 2020-10-24
59 store: boltdb-shipper
60 object_store: filesystem
61 schema: v11
62 index:
63 prefix: index_
64 period: 24h
65storage_config:
66 boltdb_shipper:
67 active_index_directory: /loki/boltdb-shipper-active
68 cache_location: /loki/boltdb-shipper-cache
69 cache_ttl: 24h # Can be increased for faster performance over longer query periods, uses more disk space
70 shared_store: filesystem
71 filesystem:
72 directory: /loki/chunks
73compactor:
74 working_directory: /loki/boltdb-shipper-compactor
75 shared_store: filesystem
76limits_config:
77 reject_old_samples: true
78 reject_old_samples_max_age: 168h
79chunk_store_config:
80 max_look_back_period: 0s
81table_manager:
82 retention_deletes_enabled: false
83 retention_period: 0s
84EOH
85 destination = "local/config.yml"
86 change_mode = "restart"
87 }
88 resources {
89 cpu = 1000 #Mhz
90 memory = 1000 #MB
91 }
92 }
93 }
94}
Rien de bien compliqué. Remarquez qu’on expose à nouveau Loki autravers de Traefik.
Déployer vector pour collecter les logs
0job "http-collection" {
1 datacenters = ["edge"]
2 type = "system"
3 group "vector" {
4 count = 1
5 network {
6 port "api" {
7 to = 8686
8 }
9 }
10 ephemeral_disk {
11 size = 500
12 sticky = true
13 }
14 task "vector" {
15 driver = "docker"
16 config {
17 image = "timberio/vector:0.46.0-alpine"
18 ports = ["api"]
19 volumes = [
20 "local/data/traefik:/vector/traefik",
21 ]
22 mounts = [
23 {
24 type = "bind"
25 target = "/var/run/docker.sock"
26 source = "/var/run/docker.sock"
27 readonly = true
28 },
29 {
30 type = "bind"
31 target = "/tmp/data"
32 source = "/opt/data"
33 readonly = true
34 }
35 ]
36 }
37 env {
38 VECTOR_CONFIG = "local/vector.toml"
39 VECTOR_REQUIRE_HEALTHY = "false"
40 }
41 resources {
42 cpu = 100 # 100 MHz
43 memory = 100 # 100MB
44 }
45 # template with Vector's configuration
46 template {
47 destination = "local/vector.toml"
48 change_mode = "signal"
49 change_signal = "SIGHUP"
50 # overriding the delimiters to [[ ]] to avoid conflicts with Vector's native templating, which also uses {{ }}
51 left_delimiter = "[["
52 right_delimiter = "]]"
53 data=<<EOH
54 data_dir = "local/data/vector/"
55 [api]
56 enabled = true
57 address = "0.0.0.0:8686"
58 playground = true
59 [sources.docker-logs]
60 type = "docker_logs"
61 [sources.http-logs]
62 type = "file"
63 include = [ "/tmp/data/traefik/log/*.log" ]
64 data_dir = "/vector/traefik"
65 [transforms.traefik-logs]
66 type = "remap"
67 inputs = [ "http-logs" ]
68 source = '''
69 . = parse_json!(.message)
70 '''
71 [sinks.machinelogs]
72 type = "loki"
73 compression = "snappy"
74 encoding.codec = "json"
75 inputs = ["docker-logs"]
76 endpoint = "http://loki.service.local
77 healthcheck.enabled = true
78 out_of_order_action = "drop"
79 # remove fields that have been converted to labels to avoid having the field twice
80 remove_label_fields = true
81 [sinks.machinelogs.labels]
82 job = "{{label.\"com.hashicorp.nomad.job_name\" }}"
83 task = "{{label.\"com.hashicorp.nomad.task_name\" }}"
84 group = "{{label.\"com.hashicorp.nomad.task_group_name\" }}"
85 namespace = "{{label.\"com.hashicorp.nomad.namespace\" }}"
86 node = "{{label.\"com.hashicorp.nomad.node_name\" }}"
87 [sinks.accesslogs]
88 type = "loki"
89 compression = "snappy"
90 encoding.codec = "json"
91 inputs = ["traefik-logs"]
92 endpoint = "http://loki.service.local
93 healthcheck.enabled = true
94 out_of_order_action = "drop"
95 # remove fields that have been converted to labels to avoid having the field twice
96 remove_label_fields = true
97 [sinks.accesslogs.labels]
98 service = "{{ .ServiceName}}"
99 router = "{{ .RouterName }}"
100 status = "{{ .DownstreamStatus }}"
101 method = "{{ .RequestMethod }}"
102 job = "/var/log/traefik.log"
103 EOH
104 }
105 kill_timeout = "30s"
106 }
107 }
108}
On collecte les logs de traefik depuis les fichiers de logs générés.
61 [sources.http-logs]
62 type = "file"
63 include = [ "/tmp/data/traefik/log/*.log" ]
64 data_dir = "/vector/traefik"
65 [transforms.traefik-logs]
66 type = "remap"
67 inputs = [ "http-logs" ]
68 source = '''
69 . = parse_json!(.message)
70 '''
Lors de la collection, Vector va injecter le continue du log dans un objet. Ainsi à la ligne 68, on pase un petit script tn VRL pour renvoyer directement le contenue log (qui est un JSON pour traefik). Et on rajoute lors de l’envoie des tabels supplémentaires.
Remarquez, qu’on expose la socket docker, pour aller collecter les logs. Une autre manière serait d’aller chercher les fichiers de logs dans le répertoire de Nomad (/opt/nomad/alloc/*/logs par exemple) vant de les process et les envoyer à Loki.
On peut désormais créer une nouvelle datasource sur Grafana, pour Loki, en utilisant l’endpoint loki.service.local
.
Et voila.
Conclusion
J’utilise ici Vector pour formater et structurer les logs avant de les envoyer à Loki. Selon le format des logs on peut changer la configuration de Vector pour les structurer et garde une cohérence sur le long terme.
On collecte tous les logs de Traefik, et comme on utilise Traefik pour assurer la redirection dynamique vers les bon services (Loki, Prometheus, Grafana), on aura des logs de toute le trafic généré par ses appels. Et peuvent rapidement venir flooder et rendre peu visible les logs de vos services. On pourrait effacer ses logs si l’on voit que le service est celui de Loki par exemple, ou créer un autre entrypoint (sur un autre port) et utiliser celui-ci pour tout le trafic non relié à nos services directement.
Enfin, Loki, comme tous les gestionnaires de logs, n’est pas fait pour exploiter rapidement de la donnée sur le long terme (12 mois). Si vous voulez utiliser ces logs pour créer des KPI, il est bien mieux d’avoir un service qui va calculer et enregistrer les valeurs des KPI comme souhaités. Vous pourrez alors avoir des 95 percentile sur les 12 derniers mois et calculer d’autres KPI, non de run mais de reporting (sur 3/6/12 mois). N’oubliez pas d’archiver les logs avant purge, pour pouvoir les réhydrater pour faire de l’archéologie si besoin.