System budowania oparty na Concourse dla iOS and Android (część 1)
Czy kiedykolwiek miałeś wrażenie, że czekanie na zarchiwizowanie aplikacji na iOS lub utworzenie pakietu APK to tylko strata czasu?
Czy nie byłoby wspaniale, gdyby istniała możliwość skupienia się na repozytorium bez martwienia się o cokolwiek innego, a zaoszczędzony czas można by poświęcić na pisaniu długich, złożonych i powtarzalnych plików konfiguracyjnych w czytelnym języku czułym na wcięcia. Brzmi interesująco? Zapraszam do artykułu.
Ciągła integracja i aplikacje mobilne
Korzystanie z rozwiązania ciągłej integracji w dowolnym przepływie pracy związanej z programowaniem ma wiele zalet:
- Umożliwia automatyczne uruchamianie testów
- Może z łatwością tworzyć różne wersje kompilacji dla różnych środowisk
- Może zautomatyzować proces wdrażania, aby zmniejszyć prawdopodobieństwo błędu ludzkiego
- Ułatwia śledzenie numerów wersji i budowanie artefaktów
- Oszczędza czas programistów (gdy działa niezawodnie)
Obecnie dostępnych jest wiele rozwiązań CI/CD opartych na chmurze. Służą jako doskonałe opcje automatyzacji przepływów pracy dla aplikacji internetowych, czy systemów serworowych. Jednak rozwój aplikacji mobilnych stwarza własne wyzwania, które często są trudne do rozwiązania za pomocą usług w chmurze.
Środowiska IDE dla platform mobilnych nie zostały zaprojektowane z myślą o automatyzacji i użyciu wiersza poleceń. Jest to szczególnie ważne w przypadku Xcode - opiera się on w dużej mierze na graficznych edytorach konfiguracji i działa tylko w systemie macOS, który nie jest pierwszym wyborem systemu operacyjnego dla środowisk serwerowych. Android Studio i Xcode są całkiem przydatne, gdy pracuje z nimi pojedynczy programista, ale w przypadku projektu w którym bierze udział wiele osób, lub kilka osób pracujących nad kilkoma projektami, ich niedociągnięcia stają się natychmiast widoczne.
Chcąc uruchomić określone testy instrumentacyjne interfejsu użytkownika w aplikacji, może powstać konieczność użycia specjalistycznej usługi umożliwiającej korzystanie z symulatorów urządzeń do przeprowadzania testów. Niektóre z nich oferują nawet płatne farmy urządzeń fizycznych. Alternatywnie, mając własne rozwiązanie CI, można skonfigurować prywatny węzeł roboczy do wykonywania testów lub nawet podłączyć urządzenia i przeprowadzać na nich testy.
Złożoność procedur podpisywania kodu i obsługi administracyjnej to kolejna kwestia, którą należy wziąć pod uwagę, próbując zautomatyzować proces wdrażania. Wersja aplikacji musi być podpisana, a zasoby do podpisywania powinny być przechowywane w bezpiecznym miejscu, najlepiej oddzielonym od kodu źródłowego. Jeśli tworzysz na iOS, profile i uprawnienia mogą przyprawić Cię o poważny ból głowy. Co więcej, nadal potrzebujesz bezpiecznego kanału komunikacji ze sklepem, aby przesłać ostateczną wersję, a to wymaga kolejnego, innego zestawu poświadczeń.
Concourse CI
Z powodów wymienionych powyżej i również po to, aby trochę poeksperymentować z ciekawymi technologiami, zdecydowaliśmy się skonfigurować wewnętrzne środowisko CI/CD zdolne do budowania i wdrażania aplikacji na Androida, iOS i Fluttera. Jeśli chodzi o rozwiązania CI typu open source, najbardziej oczywistym wyborem jest Jenkins, jednakże mając na uwadze nasze wcześniejsze doświadczenia z nim zdecydowaliśmy się poszukać innych opcji - tak właśnie znaleźliśmy Concourse.
Concourse to dość świeży system CI z wieloma interesującymi funkcjami:
- Jest rozproszony - składa się z wielu niezależnych węzłów
- Oferuje przyjazny dla użytkownika interfejs graficzny do wizualizacji statusu oraz wygodny interfejs wiersza poleceń do zarządzania
- Jest oparty na dockerze - domyślnie wszystkie kompilacje i prace na zasobach są wykonywane w kontenerach docker
- Używa plików konfiguracyjnych YAML do opisywania potoków, które mogą być utrzymywane pod kontrolą wersji w trywialny sposób
- Ma potężną architekturę zasobów, które można wykorzystać do łatwego wdrażania niestandardowych integracji i nowych funkcji
Przyjrzyjmy się bliżej tym funkcjom, by móc lepiej zrozumieć jak wyglądają w praktyce.
Podstawowa konfiguracja
Concourse jest napisany w Go oraz jest dostępny jako samodzielny plik binarny dla systemu Linux, Mac lub Windows. Maszyna, na której działa proces Concourse, stanowi węzeł. Chociaż cały system może składać się z wielu niezależnych węzłów, musi istnieć co najmniej jeden węzeł zarządzający, ponieważ będzie on odpowiedzialny za udostępnienie systemowi interfejsu WWW i planowanie zadań dla węzłów roboczych. Concourse wymaga również bazy danych PostgreSQL do przechowywania konfiguracji i metadanych kompilacji; jednak wszystkie inne artefakty kompilacji będą przechowywane w innym miejscu przy użyciu zasobów zewnętrznych.
W celu zwiększenia efektywności działania, potrzebny będzie również co najmniej jedem węzeł roboczy. Węzeł roboczy to inna maszyna z uruchomionym Concourse i demonem Dockera. Kiedy wezęł zarządzający ma kontakt z węzłem roboczym, może go poprosić o wykonanie niektórych zadań. Ponieważ wszystko dzieje się w kontenerach docker, nie ma potrzeby ręcznego konfigurowania środowiska kompilacji, gdyż węzeł ten automatycznie pobierze niezbędne obrazy dockera, pobierze kod z repozytorium git, wykona zadania kompilacji wewnątrz kontenera i prześle wszelkie artefakty do odpowiednich zasobów. Niestety nie jest to do końca prawdą w przypadku aplikacji na iOS, niedogodność ta zostanie opisana w dalszej części artykułu.
Do skonfigurowania węzłów należy użyć narzędzia wiersza poleceń fly. Umożliwia ono sprawdzanie stanu węzłów roboczych, aktualizowanie konfiguracji i wykonywanie innych zadań związanych z zarządzaniem. Więcej szczegółów na temat podstawowej konfiguracji Concourse można znaleźć w oficjalnej docs.
Potoki, zasoby, zadania i taski
Concourse używa pewnych abstrakcyjnych terminów do opisania obszarów, które mają być zautomatyzowane oraz prezentuje jak to zrobić. Przed przystąpieniem do konfigurowania projektu przydatne może być zapoznanie się z tymi terminami. Poniżej przedstawiony jest krótki przegląd najważniejszych pojęć.
Potoki
Potok to opis pojedynczego przepływu pracy ciągłej integracji. Najprawdopodobniej będzie on przydatny przy każdym projekcie, który będzie rozwijany i utrzymywany. Zostanie on zwizualizowany w interfejsie graficznym, który umożliwi podgląd stanu danego projektu oraz wyświetli historię kompilacji. Potok zawiera listę zasobów i zadań oraz deklaratywny opis ich wspólnego używania do wykonywania żądanych zadań. Na poziomie kodu potok to po prostu plik YAML z pewnymi obowiązkowymi sekcjami i ustalonym formatem. Więcej o potokach znajduje się tutaj.
Zasoby
Zasoby to jedna z podstawowych koncepcji Concourse. Reprezentują różne zewnętrzne artefakty, które mogą być używane jako dane wejściowe lub wyjściowe dla wykonywanych zadań. Zasoby są również wersjonowane i mogą być automatycznie monitorowane pod kątem zmian. Gdy Concourse wykryje, że dostępna jest nowa wersja zasobu, może wyzwolić wykonanie zadania przy użyciu nowej wersji zasobu jako danych wejściowych.
Oto kilka przykładów zasobów:
- Repozytorium git - z commitami traktowanymi jako kolejne wersje
- S3 lub podobne miejsce do przechowywania danych - miejsce, w którym można zapisać gotowy artefakt
- Automatycznie zwiększający się numer wersji
- Kanał Slack - może służyć jako wyjście do informowania o stanie kompilacji
Zaletą zasobów jest to, że są to obrazy dockera - zatem prosec tworzanie własnego zasobu jest bardzo przystępny, jeżeli te już dostępne nie spełniają wszystkich potrzeb. Udostępnianie nowego zasobu to świetny sposób na wniesienie wkładu do społeczności Concourse.
Zadania
Zadania definiują rzeczywiste akcje do wykonania w potoku. Każde zadanie posiada nazwę oraz będzie widoczne jako węzeł na wykresie w internetowym interfejsie użytkownika. Najważniejszą częścią zadania jest lista czynności do wykonania. Mogą to być między innymi:
- Pobieranie nowej wersji zasobu
- Wykonywanie zadania przy użyciu danych wejściowych dostarczonych przez zasób
- Wysłanie nowej wersji zasobu po wykonaniu na nim jakiegoś zadania
Zasoby, na których działa utworzone przez użytkownika zadanie, będą widoczne w interfejsie graficznym jako końcówki grafu danego potoku.
Taski
Task jest najmniejszą jednostką potoku, jaką można skonfigurować - zwykle jest to konkretne polecenie do wykonania. Podstawowy opis taska składa się z:
- Platformy, na której można go wykonać (potrzebny będzie węzeł roboczy z odpowiadającą platformą)
- Obrazu dockera, który zostanie użyty do wykonania zadania
- Polecenia do wykonania w kontenerze
- Listy wejść i wyjść - pasujących zasobów dostępnych dla konkretnego zadania
Po kliknięciu węzła zadania w interfejsie graficznym możliwe jest wyświetlenie listy tasków, które zadanie zawiera. Dodatkowo możliwe jest także wyświetlenie logów w celu dokładniejszego podglądu wydarzeń, które miały miejsce w danym tasku.
Podsumowanie
Mam nadzieję, że te podstawowe informacje na temat działania Concourse CI pomogą zrozumieć, jak działa ten system. W następnej części opiszemy bardziej szczegółowo, jak zastosować tę wiedzę do tworzenia działających potoków do programowania aplikacji na Androida i iOS oraz jak rozwiązać niektóre problemy, które mogą pojawić się podczas tego procesu. Do zobaczenia!