diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..4fc7273 --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,37 @@ +name: Nomad Validate +on: + pull_request: + branches: + - main + paths: + - '**.nomad' + +jobs: + tailscale: + env: + NOMAD_ADDR: ${{ secrets.NOMAD_ADDR }} + runs-on: ubuntu-22.04 + steps: + - name: Setup Tailscale + uses: tailscale/github-action@main + with: + authkey: ${{ secrets.TAILSCALE_AUTHKEY }} + nomad-plan: + needs: tailscale + runs-on: ubuntu-22.04 + steps: + - name: Get Vault + run: | + mkdir -p bin ; curl -fSL https://releases.hashicorp.com/vault/1.12.3/vault_1.12.3_linux_amd64.zip | gunzip -> bin/vault + - name: Get token + run: chmod u+x bin/vault ; bin/vault -version + - name: Get Nomad + run: | + mkdir -p bin ; curl -fSL https://releases.hashicorp.com/nomad/1.4.4/nomad_1.4.4_linux_amd64.zip | gunzip -> bin/nomad + - name: Checkout change + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Plan the job + run: chmod u+x bin/nomad ; bin/nomad plan ansible.nomad diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d71c4f2..bea8e75 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,3 +26,14 @@ repos: rev: v1.26.3 hooks: - id: tfsec-system + + - repo: local + hooks: + - id: packer-fmt + name: Packer Format + language: system + types: + - hcl + entry: packer fmt +ci: + autoupdate_branch: main diff --git a/ansible.nomad b/ansible.nomad new file mode 100644 index 0000000..fa97a24 --- /dev/null +++ b/ansible.nomad @@ -0,0 +1,36 @@ +# Job to add Ansible to all nodes, in order to allow them to +# configure themselves +# This job should install Ansible in a system-wide place. +job "ansible" { + type = "sysbatch" + datacenters = ["dc1"] + name = "Ansible" + + periodic { + cron = "@daily" + enabled = true + } + + group "nodes" { + count = 1 + + task "step-up" { + template { + change_mode = "noop" + destination = "local/install-ansible.sh" + perms = "0777" + + data = <terraform ; chmod u+x terraform"] - } - volume_mount { - volume = "scratch" - destination = "/volume" - read_only = false + args = ["-c", "curl https://r1eleases.hashicorp.com/terraform/1.3.4/terraform_1.3.4_linux_arm64.zip | gunzip ->terraform ; chmod u+x terraform"] } + // volume_mount { + // volume = "scratch" + // destination = "/volume" + // read_only = false + // } } task "check-consul" { driver = "exec" diff --git a/grafana.nomad b/grafana.nomad index cc47aa0..9fd0c1b 100644 --- a/grafana.nomad +++ b/grafana.nomad @@ -1,6 +1,6 @@ variable "grafana_version" { type = string - default = "8.5.0" + default = "9.4.7" description = "Grafana version" } @@ -25,7 +25,7 @@ job "dashboard" { # select machines with more than 4GB of RAM constraint { attribute = "${attr.memory.totalbytes}" - value = "1GB" + value = "500MB" operator = ">=" } update { @@ -62,8 +62,8 @@ job "dashboard" { type = "tcp" port = "mysql_server" name = "mysql_alive" - interval = "20s" - timeout = "2s" + interval = "30s" + timeout = "5s" } } @@ -105,7 +105,7 @@ job "dashboard" { service { name = "grafana" - tags = ["monitoring", "dashboard"] + tags = ["monitoring", "dashboard", "urlprefix-/grafana"] port = "grafana_server" check { @@ -113,8 +113,8 @@ job "dashboard" { name = "grafana-api" path = "/api/health" type = "http" - interval = "20s" - timeout = "5s" + interval = "10m" + timeout = "10s" } } @@ -141,7 +141,7 @@ job "dashboard" { } resources { cpu = 1000 - memory = 1024 + memory = 512 } config { @@ -182,7 +182,7 @@ reporting_enabled = false external_enabled = false [security] admin_user = admin -admin_password = "admin" +admin_password = "admin" #pragma: allowlist secret disable_gravatar = true [dashboards] versions_to_keep = 10 diff --git a/loki.nomad b/loki.nomad new file mode 100644 index 0000000..8aa2013 --- /dev/null +++ b/loki.nomad @@ -0,0 +1,85 @@ +variable "loki_version" { + type = string + default = "v2.7.5" +} + +job "loki" { + datacenters = ["dc1"] + type = "service" + name = "loki" + + meta { + auto-backup = true + backup-schedule = "@hourly" + backup-target-db = "postgres" + } + update { + max_parallel = 2 + health_check = "checks" + min_healthy_time = "5s" + healthy_deadline = "300s" + progress_deadline = "10m" + auto_revert = true + auto_promote = true + canary = 1 + } + priority = 80 + group "log-server" { + count = 1 + + network { + port "http" { + static = 3100 + } + port "grpc" { + static = 9096 + } + } + service { + name = "loki-http-server" + tags = ["urlprefix-/loki strip=/loki"] + port = "http" + on_update = "require_healthy" + + check { + name = "loki_ready" + type = "http" + path = "/ready" + port = "http" + interval = "10s" + timeout = "3s" + } + } + + service { + name = "loki-grpc" + port = "grpc" + } + + task "server" { + driver = "exec" + config { + command = "loki" + args = [ + "-config.file=local/loki.yml" + ] + } + resources { + cpu = 128 + memory = 200 + } + template { + data = file("loki.yml.tpl") + destination = "local/loki.yml" + change_mode = "restart" + } + artifact { + source = "https://github.com/grafana/loki/releases/download/${var.loki_version}/loki-linux-${attr.cpu.arch}.zip" + options { # checksum depends on the cpu arch + } + destination = "local/loki" + mode = "file" + } + } + } +} diff --git a/loki.yml.tpl b/loki.yml.tpl new file mode 100644 index 0000000..efcf293 --- /dev/null +++ b/loki.yml.tpl @@ -0,0 +1,53 @@ +auth_enabled: false + +server: + http_listen_port: {{ env "NOMAD_PORT_http" }} + grpc_listen_port: {{ env "NOMAD_PORT_grpc" }} + register_instrumentation: true + http_server_read_timeout: "40s" + http_server_write_timeout: "50s" + +{{/* distributor: + ring: + kvstore: + store: consul + prefix: loki/collectors */}} + +ingester: + lifecycler: + address: 127.0.0.1 + ring: + kvstore: + store: consul + prefix: loki/collectors + replication_factor: 1 + final_sleep: 0s + chunk_idle_period: 5m + chunk_retain_period: 30s + flush_op_timeout: 20m +schema_config: + configs: + - from: 2022-01-01 + store: boltdb-shipper + object_store: filesystem + schema: v11 + index: + prefix: loki_ + period: 24h + +storage_config: + boltdb_shipper: + active_index_directory: local/index + cache_location: local/index_cache + filesystem: + directory: local/index + +limits_config: + enforce_metric_name: false + reject_old_samples: true + reject_old_samples_max_age: 168h + +compactor: + working_directory: local/data/compactor + shared_store: filesystem + compaction_interval: 5m diff --git a/loki/loki-local.nomad b/loki/loki-local.nomad new file mode 100644 index 0000000..736f494 --- /dev/null +++ b/loki/loki-local.nomad @@ -0,0 +1,148 @@ +// variable "access_key" { +// type = string +// } + +// variable "secret_key" { +// type = string +// } + +variable "loki_version" { + type = string + default = "v2.7.1" +} + +job "loki" { + datacenters = ["dc1"] + type = "service" + name = "loki" + + meta { + auto-backup = true + backup-schedule = "@hourly" + backup-target-db = "postgres" + } + update { + max_parallel = 1 + health_check = "checks" + min_healthy_time = "5s" + healthy_deadline = "300s" + progress_deadline = "10m" + auto_revert = true + auto_promote = true + canary = 1 + } + priority = 80 + group "log-server" { + count = 1 + + network { + port "http" { + static = 3100 + } + port "grpc" { + static = 9096 + } + } + service { + name = "loki-http-server" + tags = [ + "urlprefix-/loki strip=/loki", + "traefik.enable=true", + "traefik.http.routers.http.rule=Path(`loki`)" + ] + port = "http" + on_update = "require_healthy" + + check { + name = "loki_ready" + type = "http" + path = "/ready" + port = "http" + interval = "10s" + timeout = "3s" + } + } + + service { + name = "loki-grpc" + port = "grpc" + } + + task "server" { + driver = "exec" + // env { + // access_key = var.access_key + // secret_key = var.secret_key + // } + config { + command = "loki" + args = [ + "-config.file=local/loki.yml" + ] + } + resources { + cpu = 128 + memory = 200 + } + template { + data = <= v1.1.0") - -// if inky_display.resolution not in ((212, 104), (250, 122)): -// w, h = inky_display.resolution -// raise RuntimeError("This example does not support {}x{}".format(w, h)) - -// inky_display.set_border(inky_display.BLACK) - -// # Details to customise your weather display - -// CITY = "Sheffield" -// COUNTRYCODE = "GB" -// WARNING_TEMP = 25.0 - - -// # Convert a city name and country code to latitude and longitude -// def get_coords(address): -// g = geocoder.arcgis(address) -// coords = g.latlng -// return coords - - -// # Query Dark Sky (https://darksky.net/) to scrape current weather data -// def get_weather(address): -// coords = get_coords(address) -// weather = {} -// res = requests.get("https://darksky.net/forecast/{}/uk212/en".format(",".join([str(c) for c in coords]))) -// if res.status_code == 200: -// soup = BeautifulSoup(res.content, "lxml") -// curr = soup.find_all("span", "currently") -// weather["summary"] = curr[0].img["alt"].split()[0] -// weather["temperature"] = int(curr[0].find("span", "summary").text.split()[0][:-1]) -// press = soup.find_all("div", "pressure") -// weather["pressure"] = int(press[0].find("span", "num").text) -// return weather -// else: -// return weather - - -// def create_mask(source, mask=(inky_display.WHITE, inky_display.BLACK, inky_display.RED)): -// """Create a transparency mask. - -// Takes a paletized source image and converts it into a mask -// permitting all the colours supported by Inky pHAT (0, 1, 2) -// or an optional list of allowed colours. - -// :param mask: Optional list of Inky pHAT colours to allow. - -// """ -// mask_image = Image.new("1", source.size) -// w, h = source.size -// for x in range(w): -// for y in range(h): -// p = source.getpixel((x, y)) -// if p in mask: -// mask_image.putpixel((x, y), 255) - -// return mask_image - - -// # Dictionaries to store our icons and icon masks in -// icons = {} -// masks = {} - -// # Get the weather data for the given location -// location_string = "{city}, {countrycode}".format(city=CITY, countrycode=COUNTRYCODE) -// weather = get_weather(location_string) - -// # This maps the weather summary from Dark Sky -// # to the appropriate weather icons -// icon_map = { -// "snow": ["snow", "sleet"], -// "rain": ["rain"], -// "cloud": ["fog", "cloudy", "partly-cloudy-day", "partly-cloudy-night"], -// "sun": ["clear-day", "clear-night"], -// "storm": [], -// "wind": ["wind"] -// } - -// # Placeholder variables -// pressure = 0 -// temperature = 0 -// weather_icon = None - -// if weather: -// temperature = weather["temperature"] -// pressure = weather["pressure"] -// summary = weather["summary"] - -// for icon in icon_map: -// if summary in icon_map[icon]: -// weather_icon = icon -// break - -// else: -// print("Warning, no weather information found!") - -// # Create a new canvas to draw on -// img = Image.open(os.path.join(PATH, "resources/backdrop.png")).resize(inky_display.resolution) -// draw = ImageDraw.Draw(img) - -// # Load our icon files and generate masks -// for icon in glob.glob(os.path.join(PATH, "resources/icon-*.png")): -// icon_name = icon.split("icon-")[1].replace(".png", "") -// icon_image = Image.open(icon) -// icons[icon_name] = icon_image -// masks[icon_name] = create_mask(icon_image) - -// # Load the FredokaOne font -// font = ImageFont.truetype(FredokaOne, 22) - -// # Draw lines to frame the weather data -// draw.line((69, 36, 69, 81)) # Vertical line -// draw.line((31, 35, 184, 35)) # Horizontal top line -// draw.line((69, 58, 174, 58)) # Horizontal middle line -// draw.line((169, 58, 169, 58), 2) # Red seaweed pixel :D - -// # Write text with weather values to the canvas -// datetime = time.strftime("%d/%m %H:%M") - -// draw.text((36, 12), datetime, inky_display.WHITE, font=font) - -// draw.text((72, 34), "T", inky_display.WHITE, font=font) -// draw.text((92, 34), u"{}°".format(temperature), inky_display.WHITE if temperature < WARNING_TEMP else inky_display.RED, font=font) - -// draw.text((72, 58), "P", inky_display.WHITE, font=font) -// draw.text((92, 58), "{}".format(pressure), inky_display.WHITE, font=font) - -// # Draw the current weather icon over the backdrop -// if weather_icon is not None: -// img.paste(icons[weather_icon], (28, 36), masks[weather_icon]) - -// else: -// draw.text((28, 36), "?", inky_display.RED, font=font) - -// # Display the weather data on Inky pHAT -// inky_display.set_image(img) -// inky_display.show() - // EOH - // destination = "local/repo/examples/phat/phat.py" - // } resources { cpu = 256 memory = 128