Coder – Self-Hosted Remote Development Platform

Coder – Self-Hosted Remote Development Platform

Cześć! W dzisiejszym wpisie chciałbym przedstawić wam jedno z najważniejszych narzędzi, które wykorzystywałem przez ostatni rok w moich codziennych obowiązkach w zespole Platform Engineering. Coder to narzędzie przeznaczone do tworzenia środowisk pracy dla grupy developerów, obsługujące różne platformy począwszy od Linuxa, przez macOS, aż po Windowsa. W tym wpisie dowiecie się jakie korzyści niesie ze sobą to rozwiązanie oraz jak utworzyć własny szablon, a następnie workspace. Zapraszam!

Co to jest Coder?

Coder to narzędzie wspierające pracę developerów umożliwiające im dostęp do ich miejsc pracy – mam na myśli miejsca, w których mogą programować, a nie krzesła i biurka 🙂 Za pomocą Codera tworzymy w infrastrukturze chmurowej (i nie tylko) wirtualne środowiska, do których nasi developerzy mogą się łączyć i pracować. Na rynku istnieje kilka podobnych narzędzi, ale to, co wyróżnia Codera to fakt, że wszystkie zasoby są tworzone pod spodem za pomocą kodu Terraform, co daje nam ogromne możliwości. Dzięki temu możemy dowolnie zaprojektować nasz workspace. W tym wpisie omówię Coder w wersji v2 pomijając płatną opcje enterprise.

Uspokoję wszystkich developerów, którzy nie znają się na Terraformie lub nie chcą się go uczyć. W rzeczywistości tylko kilka osób, które będą tworzyć tzw. Templates musi znać się na Terraformie. Pozostała część zespołu wystarczy, że będzie potrafiła połączyć się ze swoim środowiskiem. Coder v2 jest zaprojektowany w taki sposób żeby uprościć proces onbordingu użytkowników umożliwiając im korzystanie z niego bez konieczności zagłębiania się w techniczne szczegóły.

Jakie korzysci dostarcza Coder?

Dzięki temu, że Coder wykorzystuje kod Terraform do budowy środowisk dla naszych developerów możemy korzystać z dużej biblioteki dostawców Terraform. Oznacza to, że nie tylko możemy tworzyć maszyny wirtualne w chmurach AWS, GCP czy Azure, ale także wykorzystywać pody w klastrach Kubernetes, obrazy Dockera, czy nawet joby w systemie Nomad. To otwiera przed nami szerokie możliwości, pozwalając dostosować nasze środowiska do różnych potrzeb i wymagań projektów.

Coder współpracuje z dość szeroką gamą środowisk programistycznych między innymi z Visual Studio Code, produktami z rodziny JetBrains oraz środowiskiem Jupyter.

Dzięki temu narzędziu oraz zastosowaniu Terraform musimy wprowadzić schemat tworzenia środowisk developerskich, co znacząco poprawi ich skalowalność, modułowość i bezpieczeństwo. Ponadto, wprowadzanie nowych członków zespołów stanie się znacznie szybsze, ponieważ wszyscy korzystamy z tych samych rozwiązań. Coder oferuje naprawdę świetne produkty out-of-the-box, o których powiem więcej za chwilę.

Możemy zaprojektować szablony, które idealnie odpowiadają różnym grupom developerów w naszej firmie np.: szablon dla programistów javy, inny dla developerów .NET działający na systemach Windows, a jeszcze inny dla zespołu Platform Engineering z podwyższonymi uprawnieniami. Dzięki tej elastyczności możemy za pomocą jednego kliknięcia tworzyć środowisko pracy dla nowego członka zespołu lub usuwać je po jego odejściu. To nie tylko oszczędza czas, ale także pozwala uniknąć błędów oraz zapewnia spójność i bezpieczeństwo w naszych środowiskach pracy.

Architektura Codera

Architektura Codera jest dość prosta i składa się z trzech głównych elementów. Pierwszym z nich jest serwer Codera odpowiedzialny za zarządzanie środowiskami użytkowników i UI. Drugim elementem jest Workspace czyli środowisko pracy użytkownika, które jest zarządzane przez serwer główny. Ostatnią częścią są użytkownicy, którzy korzystają z tych środowisk.

W większości przypadków przy połączeniach z Workspace Coder stara się nawiązać połączenie bezpośrednie z użytkownikiem Workspace. Jeśli to się nie udaje wykorzystywany jest serwer do tunelowania połączenia.

Instalacja i uruchomienie

Instrukcje dotyczące instalacji Codera można znaleźć tutaj. Konfigurację Codera przeprowadzamy za pomocą zmiennych środowiskowych. Przy pierwszym uruchomieniu, Coder sprawdzi, czy zmienna CODER_ACCESS_URL została ustawiona. Ta zmienna jest używana do komunikacji użytkowników i agentów Codera zainstalowanych na Workspace z serwerem głównym. Jeśli nie zostanie ustawiona wówczas Coder automatycznie stworzy publiczny adres DNS, co jest opcją, z której skorzystam w tym wpisie. Więcej informacji na temat konfiguracji można znaleźć pod tym linkiem.

Skrypt instalacyjny pobiera binarkę Codera, która pełni rolę zarówno serwera, jak i interfejsu wiersza poleceń (CLI). Aby uruchomić serwer wystarczy wykonać polecenie coder server.

Następnie przechodzimy do podanej wyżej strony i wykonujemy setup ownera.

Zarządzanie użytkownikami

Coder został zaprojektowany z myślą o użytkownikach enterprise, gdzie może obsługiwać tysiące użytkowników naraz, ale sprawdzi się również w bardziej kameralnym gronie kilku do kilkudziesięciu osób. Zakładka Users udostępnia wygodny interfejs do zarządzania użytkownikami, przypisywania ról itp.

Coder Templates

Templates są jak matryca, z której tworzymy workspace dla naszych użytkowników. Coder od samego początku udostępnia wiele gotowych wzorców, z których możemy skorzystać lub je dostosować do naszych wymagań.

Templates stanowią kluczowy element Codera. Dzięki nim w łatwy sposób możemy tworzyć, usuwać i modyfikować środowiska pracy dla różnych grup deweloperów w naszej firmie. Są one tak naprawdę konfiguracją Terraform odpowiedzialną za tworzenie zasobów, na których nasz deweloper będzie pracował.

Poniżej znajduje się tylko kilka przykładowych szablonów gotowych do użycia od zaraz.

Własny template

Wersja Codera, z której korzystamy, ma już wbudowany moduł do modyfikacji szablonów za pomocą wygodnego UI. Ja jednak z przyzwyczajenia postanowiłem użyć CLI Codera, ponieważ wcześniej korzystałem z wersji 0.10, w której tworzenie szablonów było możliwe tylko w ten sposób.

W pierwszej kolejności musimy wybrać odpowiedni szablon startowy spośród dostępnych opcji. Ja zdecydowałem się na szablon „Develop in Linux on AWS EC2”. Przy tworzeniu szablonu nasz serwer wykona Terraform plan sprawdzając, czy wszystko jest w porządku. W tym miejscu ważne jest by nasz serwer miał odpowiednie uprawnienia do wykonania takich operacji.

Gdy szablon jest już gotowy do użycia możemy go pobrać. Jednakże, aby to zrobić w pierwszej kolejności musimy zalogować się do Codera przy użyciu coder login https://emarqig45uk1a.pit-1.try.coder.app/

Zostaniemy poproszeni o token. Token możemy wygenerować w ustawieniach konta.

Następnie pobieramy szablon na dysk lokalny za pomocą polecenia coder template pull aws-linux. Spowoduje to utworzenie folderu zawierającego jeden plik Terraform o nazwie main.tf, który zawiera całą konfigurację oraz plik README.md. W pliku README.md znajdziemy również plik JSON z wymaganymi uprawnieniami/politykami providera, które musi posiadać Coder, aby możliwe było stworzenie danego środowiska pracy.

Terraform Coder provider

Przed modyfikacją konfiguracji Terraform warto zapoznać się z kilkoma blokami Coder dostępnymi w tym narzędziu.

data "coder_parameter" – służy do stworzenia modyfikowalnego parametru, który możemy podawać przy tworzeniu środowiska w UI (np. możemy wybrać region lub wielkość instancji jaką chcemy przypisać do zasobu)

data "coder_workspace" "me" – służy jako wskaźnik do obecnego workspace

resource "coder_agent" – najciekawszy zasób; określa zadania dla agenta, możemy zdefiniować jakie dane ma wysyłać do serwera (np. stan CPU, memory, storage) , jakie skrypty wykonać przy starcie workspace i nie tylko

resource "coder_metadata" – współpracuje z coder_agent w celu wyświetlenia metadanych workspace w Coder UI

resource "coder_app" – za pomoca tego zasobu możemy stworzyć skrót do aplikacji w naszym workspace

Obecnie to prawie wszystkie zasoby Terraforma dostępne z rodziny Coder. Za chwilę pokażę w jaki sposób działają, ale najpierw wypchnijmy zmodyfikowany kod z powrotem do serwera za pomocą polecenia coder template push --create reddevops-linux

Poniżej fragment pliku konfiguracyjnego Terraform, który wypchnęliśmy z powrotem na serwer. Wyciąłem mniej interesujące i powtarzające się elementy.

terraform {
  required_providers {
    coder = {
      source = "coder/coder"
    }
    aws = {
      source = "hashicorp/aws"
    }
  }
}

data "coder_parameter" "region" {
  name         = "region"
  display_name = "Region"
  description  = "The region to deploy the workspace in."
  default      = "eu-central-1"
  mutable      = false
  option {
    name  = "EU (Frankfurt)"
    value = "eu-central-1"
    icon  = "/emojis/1f1ea-1f1fa.png"
  }
  option {
    name  = "EU (Ireland)"
    value = "eu-west-1"
    icon  = "/emojis/1f1ea-1f1fa.png"
  }
}

data "coder_parameter" "instance_type" {
  ...
}

provider "aws" {
  region = data.coder_parameter.region.value
}

data "coder_workspace" "me" {
}

data "aws_ami" "ubuntu" {
  ...
}

resource "coder_agent" "dev" {
  count                  = data.coder_workspace.me.start_count
  arch                   = "amd64"
  auth                   = "aws-instance-identity"
  os                     = "linux"
  startup_script_timeout = 180
  startup_script         = <<-EOT
    set -e

    # install and start code-server
    curl -fsSL https://code-server.dev/install.sh | sh -s -- --method=standalone --prefix=/tmp/code-server --version 4.11.0
    coder dotfiles --yes git@github.com:red-devops/dotfile-jarek.git
    >/tmp/code-server.log 2>&1 &
  EOT

  metadata {
    key          = "cpu"
    display_name = "CPU Usage"
    interval     = 5
    timeout      = 5
    script       = "coder stat cpu"
  }
  metadata {
    key          = "memory"
    display_name = "Memory Usage"
    interval     = 5
    timeout      = 5
    script       = "coder stat mem"
  }
  metadata {
    key          = "disk"
    display_name = "Disk Usage"
    interval     = 600 # every 10 minutes
    timeout      = 30  # df can take a while on large filesystems
    script       = "coder stat disk --path $HOME"
  }
}

locals {
  linux_user = "coder"
  user_data = data.coder_workspace.me.start_count > 0 ? trimspace(<<EOT

...
EOT
  ) : ""
}

resource "aws_instance" "dev" {
  ami               = data.aws_ami.ubuntu.id
  availability_zone = "${data.coder_parameter.region.value}a"
  instance_type     = data.coder_parameter.instance_type.value

  user_data = local.user_data
  tags = {
    Name = "coder-${data.coder_workspace.me.owner}-${data.coder_workspace.me.name}"
    # Required if you are using our example policy, see template README
    Coder_Provisioned = "true"
  }
}

resource "coder_metadata" "workspace_info" {
  resource_id = aws_instance.dev.id
  item {
    key   = "region"
    value = data.coder_parameter.region.value
  }
  item {
    key   = "instance type"
    value = aws_instance.dev.instance_type
  }
  item {
    key   = "disk"
    value = "${aws_instance.dev.root_block_device[0].volume_size} GiB"
  }
}

resource "aws_ec2_instance_state" "dev" {
  instance_id = aws_instance.dev.id
  state       = data.coder_workspace.me.transition == "start" ? "running" : "stopped"
}

Analizując powyższy plik możemy zauważyć kilka interesujących rzeczy. Po pierwsze, Coder posiada wiele przydatnych poleceń w interfejsie wiersza poleceń, które upraszczają wiele czynności związanych z tworzeniem i konfigurowaniem środowisk pracy. Na przykład coder_agent ma zdefiniowane kilka metadanych, a ich pobieranie jest możliwe za pomocą poleceń CLI, takich jak coder stat cpu, coder stat mem, coder stat disk --path $HOME.

W przypadku provisioningu możemy użyć polecenia coder dotfiles --yes git@github.com:red-devops/dotfile-jarek.git, które pozwala na automatyczne zainstalowanie personalnych ustawień tzw. dotfiles.

Automatyzacja Dotfiles

Jeśli nie wiesz, co to są dotfiles gorąco zachęcam do zapoznania się z tym tematem. Jakiś czas temu stworzyłem wpis na ten temat, gdzie za pomocą Ansibla automatycznie konfiguruję środowisko pracy pod swoje wymagania. Zapraszam do przeczytania wpisu pod poniższym linkiem:
https://red-devops.pl/spersonalizowane-dotfiles-dla-devops-a/

Dotfile i autoryzacja po kluczu ssh

Warto również wspomnieć, że powyższe rozwiązanie działa dla dotfiles znajdujących się w publicznym repo, ale co w przypadku, gdy chcemy użyć prywatnych? Coder dodaje kolejną wyjątkowo przydatną funkcjonalność. Po utworzeniu konta generuje dla nas parę kluczy SSH, które są dostępne dla naszych środowisk pracy. Wystarczy, że z interfejsu użytkownika Codera skopiujemy klucz publiczny i dodamy go do kluczy zaufanych w repozytoriach, z których korzystamy.

Workspace

Wracając do szablonu, który wypchnęliśmy na serwer Coder. Na poniższym zdjęciu widzimy domyślne parametry, jakie posiada ten szablon, oraz jakie zasoby zostaną utworzone. W tym przypadku będzie to tylko aws_instance.

Poniższe parametry zostały zdefiniowane w blokach data "coder_parameter" "region" oraz data "coder_parameter" "instance_type".

Bardzo wygodną funkcjonalnością jest przekazywanie logów z procesu tworzenia środowiska pracy w czasie rzeczywistym. W pierwszej kolejności zobaczymy kod Terraforma, który wykonuje proces provisioningu zasobów, a następnie logi z wykonywania skryptu user data oraz instalacji dotfile za pomocą Ansibla.

Nad oknem logowania możemy także zauważyć metadane przekazywane przez agenta, takie jak zużycie CPU, dysku i pamięci.

Opcje połączeń

Gdy nasz workspace zakończy instalację możemy się z nim połączyć na kilka różnych sposobów. Możemy użyć wygodnych przycisków np. VS Code Desktop, który otworzy VS Code na naszej maszynie i wykorzystując wtyczkę Codera połączy się bezpośrednio z naszym workspace. Inną opcją jest połączenie się za pomocą przycisku Terminal, co spowoduje otwarcie okna w przeglądarce i umożliwi pracę z wykorzystaniem tunelowania przez serwer Codera.

Alternatywnie możemy skorzystać z połączenia SSH za pomocą komendy coder ssh lub wykorzystać zasób coder_app i przycisk Ports (port forwarding), który w tym szablonie jest wyłączony. Możemy też bardziej tradycyjnie wykorzystać polecenie ssh. Istnieje wiele różnych metod połączenia się z workspace.

Warto wiedzieć

Poniżej znajdują się dodatkowe informacje, które mogą być przydatne podczas pracy z tym narzędziem.

Coder pozwala na automatyczne uruchamianie i zatrzymywanie naszych środowisk. Poniżej przedstawiam panel, w którym możemy ustawić takie opcje.

Wszystkie dane, z którymi pracuje Coder są domyślnie zapisywane w wbudowanej bazie danych PostgreSQL, ale możemy przenieść je do zewnętrznego źródła. Istnieje również opcja utworzenia serwerów Codera w formie wysokiej dostępności (High Availability), ale jest to funkcja Enterprise dlatego ją pominąłem.

Wszystkie zasoby jakie tworzymy za pomocą kodu Terraform w Coderze są domyślnie persistent, ale nie zwalnia nas to z myślenia. Musimy uważać na to co modyfikujemy w szablonach dlatego przydatna jest opcja ignore_changes w konfiguracji Terraform.

Podsumowanie

To już wszystko czym chciałem podzielić się z Wami w tym materiale. Być może było to nieco chaotyczne, ale mam nadzieję, że wpis okaże się pomocny. Moim zdaniem Coder jest obowiązkowym punktem na roadmapie jeśli chcemy tworzyć zróżnicowane, łatwo modyfikowalne środowiska pracy dla różnych zespołów. Dla administratorów i osób odpowiedzialnych za Platform Engineering Coder powinien być to jednym z pierwszych kroków do wdrożenia. Liczba gotowych rozwiązań i łatwość ich użycia znacząco przyspieszy wprowadzanie nowych członków do projektów. Możliwość dodatkowego personalizowania środowiska sprawi, że przy pracy z tym narzędziem poczujemy się jak u siebie.

Comments are closed.