Skip to content

Instantly share code, notes, and snippets.

@nivekuil
Created November 21, 2020 11:45
Show Gist options
  • Save nivekuil/05f507d9e53cf53da6918728ece54b40 to your computer and use it in GitHub Desktop.
Save nivekuil/05f507d9e53cf53da6918728ece54b40 to your computer and use it in GitHub Desktop.
(defn enable-traefik [m port]
(into {}
(map (fn [[k v]]
{k (update-in v [:deploy :labels] assoc
(format "traefik.http.services.%s.loadbalancer.server.port" (name k)) port
"traefik.enable" "true"
"traefik.docker.network" :net/traefik)}))
m))
(defn enable-prometheus [m port]
(into {}
(map (fn [[k v]]
{k (update-in v [:deploy :labels] assoc
"prometheus.job" k
"prometheus.port" port)}))
m))
(defn bind-docker-socket [m]
(into {}
(map (fn [[k v]]
{k (update v :volumes conj "/var/run/docker.sock:/var/run/docker.sock:ro")}))
m))
(defn flags [& pairs]
(mapv (fn [[k v]] (str "--" k "=" v)) (partition 2 pairs)))
(defn traefik-with-cloudflare
"Use traefik with cloudflare.
If .tls=true on routers, then full mode ssl must be used. Otherwise
if Cloudflare is proxying unencrypted traffic, the Traefik router
must leave off .tls=true. TODO enforce that somehow; rules engine?"
[m]
(into {}
(map (fn [[k v]]
{k (-> v
(update :command concat
(flags
"certificatesResolvers.myresolver.acme.dnsChallenge" "true"
"certificatesResolvers.myresolver.acme.dnsChallenge.provider" "cloudflare"))
(assoc :environment {"CLOUDFLARE_EMAIL" cloudflare-email
"CLOUDFLARE_API_KEY" cloudflare-global-api-key}))}))
m))
(defn traefik-access-log
""
[m]
(into {}
(map (fn [[k v]]
{k (-> v
(update :command concat
(flags
"accesslog" true
"accesslog.format" "json"
"accesslog.bufferingsize" 10)))}))
m))
(def traefik-stack
{:stack/name "traefik"
:stack/compose
{:networks {:net/traefik {:external true}}
:services
(->
{:ser/traefik
{:image "traefik:2.3.2"
:command (flags
"api.dashboard" true
"log.format" "json"
"providers.docker" true
"providers.docker.swarmMode" true
"providers.docker.exposedByDefault" false
"providers.docker.endpoint" "unix:///var/run/docker.sock"
"entrypoints.web.address" ":80"
"entrypoints.websecure.address" ":443"
"metrics.prometheus" true)
:networks #{:net/traefik}
:ports #{;; bind to host, not through swarm, to log the real client IP
{:target 80
:published 80
:mode :host}
{:target 443
:published 443
:mode :host}}
:deploy
{:labels
{"traefik.http.routers.api.rule" (-> "Host(`%s`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))"
(format hostname))
"traefik.http.routers.api.service" "api@internal"
"traefik.http.routers.api.middlewares" "auth"
"traefik.http.routers.api.tls" "true"
"traefik.enable" "true"
"traefik.http.services.swarm-dummy.loadbalancer.server.port" 42069
"traefik.http.middlewares.auth.basicauth.users" traefik-basicauth-credentials
"traefik.http.middlewares.auth.basicauth.headerField" "X-WebAuth-User" ; passed to grafana
"traefik.http.middlewares.auth.basicauth.removeheader" "true" ; avoid conflict with grafana
}
:placement {:constraints ["node.role == manager"]}}}}
traefik-access-log
traefik-with-cloudflare
(enable-prometheus 8080)
bind-docker-socket)}})
(def monitoring-stack
{:stack/name "monitoring"
:stack/compose
{:networks {:net/traefik {:external true}}
:volumes {:vol/grafana {:external true}
:vol/prometheus {:external true}}
:services
(merge
(->
{:ser/prometheus
{:image "prom/prometheus:latest"
:user "root" ;TODO this is sad https://github.com/prometheus/prometheus/pull/7420
:volumes #{"./prometheus/prometheus.yml:/prometheus.yml"
{:type :volume
:source :vol/prometheus
:target "/prometheus"}}
:command ["--config.file=/prometheus.yml"]
:networks #{:net/traefik :net/default}}}
bind-docker-socket)
(->
{:ser/grafana
{:image "grafana/grafana:latest"
:environment {"GF_SERVER_ROOT_URL" (str "http://" hostname "/grafana")
"GF_SERVER_SERVE_FROM_SUB_PATH" 1
"GF_AUTH_PROXY_ENABLED" 1
"GF_AUTH_PROXY_HEADER_NAME" "X-WEBAUTH-USER"
"GF_AUTH_PROXY_AUTO_SIGN_UP" 1}
:volumes #{{:type :volume
:source :vol/grafana
:target "/var/lib/grafana"}}
:networks #{:net/traefik :net/default}
:deploy
{:labels {"traefik.http.routers.grafana.rule" (-> "Host(`%s`) && PathPrefix(`/grafana`)"
(format hostname))
"traefik.http.routers.grafana.service" :ser/grafana
"traefik.http.routers.grafana.tls" "true"
"traefik.http.routers.grafana.middlewares" "auth"}}}}
(enable-traefik 3000))
(->
{:ser/node-exporter
{:image "prom/node-exporter:v1.0.1"
:volumes #{"/proc:/host/proc:ro"
"/sys:/host/sys:ro"
"/:/rootfs:ro"}
:command (flags "path.procfs" "/host/proc"
"path.sysfs" "/host/sys"
"collector.filesystem.ignored-mount-points" "^/(sys|proc|dev|host|etc|rootfs/var/lib/docker/containers|rootfs/var/lib/docker/overlay2|rootfs/run/docker/netns|rootfs/var/lib/docker/aufs)($$|/)")
:networks #{:net/default}
:deploy {:mode :global}}}
(enable-prometheus 9100))
(->
{:ser/cadvisor
{:image "gcr.io/google-containers/cadvisor:latest"
:volumes #{"/:/rootfs:ro"
"/var/run:/var/run:rw"
"/sys:/sys:ro"
"/var/lib/docker/:/var/lib/docker:ro"}
:command ["--docker_only=true"]
:networks #{:net/default}
:deploy {:mode :global}}}
(enable-prometheus 8080))
{:ser/loki
{:image "grafana/loki:2.0.0"
:command ["-config.file=/etc/loki/loki.yaml"]
:networks #{:net/default}
:volumes #{"./loki:/etc/loki"}}}
(-> {:ser/vector
{:image "timberio/vector:latest-alpine"
:command ["--watch-config"]
:networks #{:net/default}
:volumes #{"./vector:/etc/vector:ro"}}}
bind-docker-socket))}})
(defn replace-ns [m]
(update m :stack/compose
(fn [compose]
(walk/postwalk #(if (qualified-keyword? %) (name %) %) compose))))
(defn add-version [m]
(update m :stack/compose assoc :version compose-file-version))
(def stack-transforms (comp replace-ns add-version))
(def stacks (->> [traefik-stack monitoring-stack]
(mapv stack-transforms)))
(defn deploy [{:stack/keys [name compose]}]
(str "docker stack deploy --compose-file - " name " <<< '" (json/generate-string compose) "'"))
(doseq [stack stacks]
(spit (str "stack-" (:stack/name stack)) (deploy stack)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment