Ewolucja CI w zespole programistów mobilnych

Obecnie większość oprogramowania jest opracowywana w zespołach. Warunki pomyślnego rozwoju zespołu można przedstawić w formie prostego diagramu.

Ewolucja CI w zespole programistów mobilnych

Po napisaniu kodu musisz upewnić się, że:

  1. To działa
  2. Niczego nie psuje, łącznie z kodem, który napisali twoi koledzy.

Jeśli oba warunki zostaną spełnione, jesteś na dobrej drodze do sukcesu. Aby łatwo sprawdzić te warunki i nie zejść z opłacalnej ścieżki, wymyśliliśmy Continuous Integration.

CI to przepływ pracy, w którym integrujesz swój kod z ogólnym kodem produktu tak często, jak to możliwe. I nie tylko integrujesz, ale także stale sprawdzasz, czy wszystko działa. Ponieważ trzeba dużo i często sprawdzać, warto pomyśleć o automatyzacji. Możesz sprawdzić wszystko ręcznie, ale nie powinieneś i oto dlaczego.

  • Drodzy ludzie. Godzina pracy dowolnego programisty jest droższa niż godzina pracy dowolnego serwera.
  • Ludzie popełniają błędy. Dlatego mogą wystąpić sytuacje, gdy testy zostały uruchomione na niewłaściwej gałęzi lub skompilowano niewłaściwe zatwierdzenie dla testerów.
  • Ludzie są leniwi. Od czasu do czasu, gdy kończę jakieś zadanie, pojawia się myśl: „Co tu jest do sprawdzenia? Napisałem dwie linijki - wszystko działa! Myślę, że niektórzy z Was też czasami miewają takie myśli. Ale zawsze należy sprawdzić.

Jak wdrożono i rozwijano ciągłą integrację w zespole programistów mobilnych Avito, jak od 0 do 450 kompilacji dziennie i jak maszyny budujące montują 200 godzin dziennie, mówi Nikołaj Niestierow (Niestierow) jest uczestnikiem wszystkich zmian ewolucyjnych aplikacji CI/CD na Androida.

Historia oparta jest na przykładzie polecenia z Androida, ale większość podejść można zastosować również na iOS.


Dawno, dawno temu w zespole Avito Android pracowała jedna osoba. Z założenia nie potrzebował niczego od Ciągłej Integracji: nie było z kim się integrować.

Ale aplikacja rosła, pojawiało się coraz więcej nowych zadań, a zespół odpowiednio się powiększył. W pewnym momencie nadszedł czas na bardziej formalne ustanowienie procesu integracji kodu. Zdecydowano się na wykorzystanie przepływu Git.

Ewolucja CI w zespole programistów mobilnych

Koncepcja przepływu Git jest dobrze znana: projekt ma jedną wspólną gałąź deweloperską i dla każdej nowej funkcji programiści wycinają osobną gałąź, zatwierdzają ją, wypychają, a kiedy chcą połączyć swój kod z gałęzią deweloperską, otwierają prośba o pociągnięcie. Aby dzielić się wiedzą i omawiać podejścia, wprowadziliśmy code review, czyli współpracownicy muszą nawzajem sprawdzać i potwierdzać swój kod.

Czeki

Oglądanie kodu oczami jest fajne, ale nie wystarczające. Dlatego wprowadzane są automatyczne kontrole.

  • Przede wszystkim sprawdzamy Montaż ARKI.
  • Dużo Testy Junita.
  • Rozważamy pokrycie kodu, ponieważ przeprowadzamy testy.

Aby zrozumieć, jak należy przeprowadzać te kontrole, przyjrzyjmy się procesowi programowania w Avito.

Można to przedstawić schematycznie w następujący sposób:

  • Programista pisze kod na swoim laptopie. Możesz tutaj uruchomić sprawdzanie integracji - albo za pomocą haka zatwierdzenia, albo po prostu uruchamiać sprawdzanie w tle.
  • Po tym jak programista wypchnął kod, otwiera żądanie ściągnięcia. Aby jego kod został włączony do gałęzi deweloperskiej należy przejść przez recenzję kodu i zebrać wymaganą liczbę potwierdzeń. Tutaj możesz włączyć kontrole i kompilacje: dopóki wszystkie kompilacje nie zakończą się pomyślnie, żądania ściągnięcia nie można scalić.
  • Po połączeniu żądania ściągnięcia i włączeniu kodu do opracowania możesz wybrać dogodny moment: na przykład w nocy, kiedy wszystkie serwery są wolne, i przeprowadzić dowolną liczbę kontroli.

Nikt nie lubił przeprowadzać skanów na swoim laptopie. Kiedy programista ukończy jakąś funkcję, chce ją szybko wypchnąć i otworzyć żądanie ściągnięcia. Jeśli w tym momencie zostaną uruchomione długie kontrole, jest to nie tylko niezbyt przyjemne, ale także spowalnia rozwój: podczas gdy laptop coś sprawdza, nie można nad nim normalnie pracować.

Bardzo podobało nam się przeprowadzanie kontroli w nocy, ponieważ jest dużo czasu i serwerów, po których można się włóczyć. Ale niestety, gdy kod funkcji zaczyna się rozwijać, programista ma znacznie mniejszą motywację do naprawiania błędów znalezionych przez CI. Co jakiś czas łapałem się na tym, że gdy przeglądałem wszystkie błędy znalezione w porannym raporcie, myślałem, że naprawię je kiedyś później, bo teraz w Jirze pojawiło się nowe, fajne zadanie, które chcę po prostu zacząć robić.

Jeśli kontrole blokują żądanie ściągnięcia, motywacja jest wystarczająca, ponieważ dopóki kompilacje nie zmienią koloru na zielony, kod nie zostanie uruchomiony, co oznacza, że ​​zadanie nie zostanie ukończone.

W rezultacie wybraliśmy następującą strategię: maksymalny możliwy zestaw kontroli przeprowadzamy w nocy, a najbardziej krytyczne z nich i co najważniejsze, najszybsze, uruchamiamy na żądanie ściągnięcia. Ale na tym nie poprzestaniemy – równolegle optymalizujemy szybkość kontroli, aby przenieść je z trybu nocnego do kontroli żądań ściągnięcia.

W tamtym czasie wszystkie nasze kompilacje były ukończone dość szybko, więc po prostu uwzględniliśmy kompilację ARK, testy Junit i obliczenia pokrycia kodu jako blokadę żądania ściągnięcia. Włączyliśmy to, pomyśleliśmy o tym i porzuciliśmy pokrycie kodu, ponieważ myśleliśmy, że tego nie potrzebujemy.

Całkowite skonfigurowanie podstawowego CI zajęło nam dwa dni (w dalszej części szacunkowy czas jest przybliżony, potrzebny do uzyskania skali).

Potem zaczęliśmy myśleć dalej - czy w ogóle sprawdzamy poprawnie? Czy poprawnie uruchamiamy kompilacje na żądanie ściągnięcia?

Kompilację rozpoczęliśmy od ostatniego zatwierdzenia gałęzi, z której otwarto żądanie ściągnięcia. Ale testy tego zatwierdzenia mogą jedynie wykazać, że kod napisany przez programistę działa. Ale nie dowodzą, że niczego nie złamał. W rzeczywistości musisz sprawdzić stan gałęzi rozwojowej po połączeniu z nią funkcji.

Ewolucja CI w zespole programistów mobilnych

Aby to zrobić, napisaliśmy prosty skrypt bash premerge.sh:

#!/usr/bin/env bash

set -e

git fetch origin develop

git merge origin/develop

Tutaj wszystkie najnowsze zmiany z wersji deweloperskiej są po prostu pobierane i łączone z bieżącą gałęzią. Dodaliśmy skrypt premerge.sh jako pierwszy krok we wszystkich kompilacjach i zaczęliśmy sprawdzać dokładnie, czego chcemy, to znaczy integracja.

Zlokalizowanie problemu, znalezienie rozwiązania i napisanie tego skryptu zajęło trzy dni.

Aplikacja się rozwijała, pojawiało się coraz więcej zadań, zespół rósł, a premerge.sh czasami zaczynał nas zawodzić. Develop miał sprzeczne zmiany, które zepsuły kompilację.

Przykład tego, jak to się dzieje:

Ewolucja CI w zespole programistów mobilnych

Dwóch programistów jednocześnie rozpoczyna pracę nad funkcjami A i B. Twórca funkcji A odkrywa nieużywaną funkcję w projekcie answer() i niczym dobry harcerz usuwa go. Jednocześnie twórca funkcji B dodaje nowe wywołanie tej funkcji w swojej gałęzi.

Programiści kończą pracę i jednocześnie otwierają żądanie ściągnięcia. Kompilacje zostają uruchomione, premerge.sh sprawdza oba żądania ściągnięcia pod kątem najnowszego stanu rozwoju — wszystkie kontrole mają kolor zielony. Następnie żądanie ściągnięcia funkcji A jest łączone, żądanie ściągnięcia funkcji B jest łączone... Bum! Przerwy w opracowywaniu, ponieważ kod programistyczny zawiera wywołanie nieistniejącej funkcji.

Ewolucja CI w zespole programistów mobilnych

Kiedy nie ma zamiaru się rozwijać, to tak lokalna katastrofa. Cały zespół nie może niczego zebrać i przekazać do testów.

Tak się złożyło, że najczęściej zajmowałem się zadaniami infrastrukturalnymi: analityką, siecią, bazami danych. Oznacza to, że to ja napisałem te funkcje i klasy, z których korzystają inni programiści. Z tego powodu bardzo często znajdowałem się w podobnych sytuacjach. Nawet to zdjęcie wisiało przez jakiś czas.

Ewolucja CI w zespole programistów mobilnych

Ponieważ nam to nie odpowiadało, zaczęliśmy badać możliwości uniknięcia tego.

Jak nie przerwać rozwoju

Pierwsza opcja: odbuduj wszystkie żądania ściągnięcia podczas aktualizacji development. Jeśli w naszym przykładzie żądanie ściągnięcia z funkcją A jest pierwszym, które zostanie uwzględnione w opracowywaniu, żądanie ściągnięcia funkcji B zostanie przebudowane i odpowiednio sprawdzenie zakończy się niepowodzeniem z powodu błędu kompilacji.

Aby zrozumieć, jak długo to zajmie, rozważ przykład z dwoma PR. Otwieramy dwa PR: dwie kompilacje, dwie serie kontroli. Po połączeniu pierwszego PR z deweloperem, drugi wymaga przebudowania. W sumie dwa PR wymagają trzech serii kontroli: 2 + 1 = 3.

W zasadzie jest w porządku. Ale spojrzeliśmy na statystyki i typowa sytuacja w naszym zespole to 10 otwartych PR, a wtedy liczba kontroli jest sumą progresji: 10 + 9 +... + 1 = 55. Czyli przyjąć 10 PR-owcy, musicie odbudować 55 razy. I to w idealnej sytuacji, gdy wszystkie kontrole przechodzą za pierwszym razem, gdy w trakcie przetwarzania tych kilkunastu nikt nie otwiera dodatkowego pull requesta.

Wyobraź sobie siebie jako programistę, który musi jako pierwszy kliknąć przycisk „scal”, ponieważ jeśli zrobi to sąsiad, będziesz musiał poczekać, aż wszystkie kompilacje zostaną ponownie zakończone... Nie, to nie zadziała , poważnie spowolni rozwój.

Drugi możliwy sposób: zbieraj żądania ściągnięcia po sprawdzeniu kodu. Oznacza to, że otwierasz żądanie ściągnięcia, zbierasz wymaganą liczbę zatwierdzeń od współpracowników, poprawiasz to, co jest potrzebne, a następnie uruchamiasz kompilacje. Jeśli się powiedzie, żądanie ściągnięcia zostanie scalone z poleceniem development. W takim przypadku nie ma dodatkowych ponownych uruchomień, ale sprzężenie zwrotne jest znacznie spowolnione. Jako programista, kiedy otwieram żądanie ściągnięcia, od razu chcę sprawdzić, czy zadziała. Na przykład, jeśli test się nie powiedzie, musisz szybko go naprawić. W przypadku opóźnionej kompilacji, sprzężenie zwrotne spowalnia, a tym samym cały rozwój. To też nam nie odpowiadało.

W rezultacie pozostała tylko trzecia opcja - rower. Cały nasz kod, wszystkie nasze źródła są przechowywane w repozytorium na serwerze Bitbucket. W związku z tym musieliśmy opracować wtyczkę do Bitbucket.

Ewolucja CI w zespole programistów mobilnych

Ta wtyczka zastępuje mechanizm łączenia żądań ściągnięcia. Początek jest standardowy: otwiera się PR, uruchamiane są wszystkie zestawy, kończy się przegląd kodu. Jednak po zakończeniu przeglądu kodu i decyzji programisty o kliknięciu „scal”, wtyczka sprawdza, w jakim stanie deweloperskim przeprowadzono kontrole. Jeśli program deweloperski został zaktualizowany po kompilacjach, wtyczka nie pozwoli na połączenie takiego żądania ściągnięcia z gałęzią główną. Po prostu ponownie uruchomi kompilację stosunkowo niedawnego opracowania.

Ewolucja CI w zespole programistów mobilnych

W naszym przykładzie ze sprzecznymi zmianami takie kompilacje nie powiodą się z powodu błędu kompilacji. W związku z tym twórca funkcji B będzie musiał poprawić kod, ponownie uruchomić sprawdzanie, a następnie wtyczka automatycznie zastosuje żądanie ściągnięcia.

Przed wdrożeniem tej wtyczki na jedno żądanie ściągnięcia przypadało średnio 2,7 przeglądów. Z wtyczką odbyło się 3,6 uruchomień. To nam odpowiadało.

Warto zauważyć, że ta wtyczka ma wadę: restartuje kompilację tylko raz. Oznacza to, że nadal istnieje małe okno, przez które mogą przedostać się sprzeczne zmiany. Jednak prawdopodobieństwo tego jest niskie, dlatego dokonaliśmy kompromisu między liczbą uruchomień a prawdopodobieństwem niepowodzenia. Przez dwa lata odpalił tylko raz, więc chyba nie poszło na marne.

Napisanie pierwszej wersji wtyczki Bitbucket zajęło nam dwa tygodnie.

Nowe kontrole

Tymczasem nasz zespół ciągle się powiększał. Dodano nowe kontrole.

Pomyśleliśmy: po co popełniać błędy, skoro można im zapobiec? I dlatego wdrożyli statyczna analiza kodu. Zaczęliśmy od lint, który jest zawarty w zestawie SDK systemu Android. Ale wtedy w ogóle nie wiedział, jak pracować z kodem Kotlin, a my mieliśmy już 75% aplikacji napisanych w Kotlinie. Dlatego do lint dodano wbudowane Sprawdza Android Studio.

Aby to zrobić, musieliśmy dokonać wielu wypaczeń: pobrać Android Studio, spakować go w Dockerze i uruchomić na CI z wirtualnym monitorem, tak aby myślał, że działa na prawdziwym laptopie. Ale to zadziałało.

W tym też czasie zaczęliśmy dużo pisać testy oprzyrządowania i wdrożone testowanie zrzutów ekranu. Dzieje się tak wtedy, gdy dla osobnego małego widoku generowany jest zrzut ekranu referencyjnego, a test polega na wykonaniu zrzutu ekranu z widoku i porównaniu go bezpośrednio piksel po pikselu ze standardowym. Jeśli występuje rozbieżność, oznacza to, że układ gdzieś się popsuł lub coś jest nie tak w stylach.

Ale testy oprzyrządowania i testy zrzutów ekranu muszą być uruchamiane na urządzeniach: na emulatorach lub na prawdziwych urządzeniach. Biorąc pod uwagę, że testów jest dużo i są one przeprowadzane często, potrzebna jest cała farma. Założenie własnej farmy jest zbyt pracochłonne, dlatego znaleźliśmy gotową opcję – Firebase Test Lab.

laboratorium testowe Firebase

Wybrano go, ponieważ Firebase jest produktem Google, co oznacza, że ​​powinien być niezawodny i mało prawdopodobne, aby kiedykolwiek umarł. Ceny są przystępne: 5 dolarów za godzinę pracy prawdziwego urządzenia, 1 dolarów za godzinę pracy emulatora.

Wdrożenie Firebase Test Lab w naszym CI zajęło około trzech tygodni.

Ale zespół nadal się rozrastał, a Firebase, niestety, zaczął nas zawieść. Nie posiadał wówczas żadnego SLA. Czasami Firebase kazał nam czekać, aż wymagana liczba urządzeń będzie wolna do testów i nie rozpoczynał ich wykonywania od razu, tak jak byśmy tego chcieli. Oczekiwanie w kolejce trwało nawet pół godziny, czyli bardzo długo. Testy oprzyrządowania były przeprowadzane na każdym PR, opóźnienia naprawdę spowalniały rozwój, a potem miesięczny rachunek wynosił okrągłą sumę. Ogólnie rzecz biorąc, zdecydowano się porzucić Firebase i pracować wewnętrznie, ponieważ zespół wystarczająco się rozrósł.

Docker + Python + bash

Wzięliśmy Dockera, upchnęliśmy w nim emulatory, napisaliśmy prosty program w Pythonie, który w odpowiednim momencie wywołuje wymaganą liczbę emulatorów w wymaganej wersji i zatrzymuje je w razie potrzeby. I oczywiście kilka skryptów basha – gdzie byśmy byli bez nich?

Stworzenie własnego środowiska testowego zajęło pięć tygodni.

W rezultacie dla każdego żądania ściągnięcia istniała obszerna lista kontroli blokujących scalanie:

  • montaż ARK;
  • testy Junita;
  • Szarpie;
  • Sprawdza Android Studio;
  • Testy oprzyrządowania;
  • Testy zrzutów ekranu.

Zapobiegło to wielu możliwym awariom. Technicznie wszystko działało, lecz twórcy narzekali, że czas oczekiwania na rezultaty był zbyt długi.

Jak długo jest za długo? Przesłaliśmy dane z Bitbucket i TeamCity do systemu analitycznego i zdaliśmy sobie z tego sprawę średni czas oczekiwania 45 minut. Oznacza to, że programista otwierając żądanie ściągnięcia, czeka średnio 45 minut na wyniki kompilacji. Moim zdaniem to dużo i nie można tak pracować.

Oczywiście postanowiliśmy przyspieszyć wszystkie nasze kompilacje.

Przyspieszmy

Widząc, że kompilacje często stoją w kolejce, pierwszą rzeczą, którą robimy, jest kupił więcej sprzętu — rozbudowa ekstensywna jest najprostsza. Kompilacje przestały stać w kolejce, ale czas oczekiwania skrócił się tylko nieznacznie, ponieważ same kontrole trwały bardzo długo.

Usuwanie czeków, które trwają zbyt długo

Nasza ciągła integracja może wychwycić tego typu błędy i problemy.

  • Nie zamierza. CI może wychwycić błąd kompilacji, gdy coś nie zostanie zbudowane z powodu sprzecznych zmian. Jak już mówiłem, wtedy nikt nie jest w stanie nic złożyć, rozwój się zatrzymuje i wszyscy się denerwują.
  • Błąd w zachowaniu. Na przykład, gdy aplikacja jest zbudowana, ale ulega awarii po naciśnięciu przycisku lub przycisk w ogóle nie jest naciśnięty. To źle, bo taki błąd może dotrzeć do użytkownika.
  • Błąd w układzie. Na przykład przycisk został kliknięty, ale przesunął się o 10 pikseli w lewo.
  • Wzrost długu technicznego.

Po przejrzeniu tej listy zdaliśmy sobie sprawę, że tylko pierwsze dwa punkty są krytyczne. Chcemy najpierw wyłapać takie problemy. Błędy w układzie są wykrywane na etapie przeglądu projektu i można je wtedy łatwo poprawić. Radzenie sobie z długiem technicznym wymaga osobnego procesu i planowania, dlatego zdecydowaliśmy się nie testować go na żądanie ściągnięcia.

Na podstawie tej klasyfikacji wstrząsnęliśmy całą listą kontroli. Przekreślono Lint i przełożył jego uruchomienie z dnia na dzień: tylko po to, aby sporządzić raport na temat liczby problemów w projekcie. Zgodziliśmy się pracować osobno z długiem technicznym i Całkowicie porzucono kontrole Android Studio. Android Studio w Dockerze do przeprowadzania inspekcji brzmi interesująco, ale sprawia sporo problemów w obsłudze. Każda aktualizacja wersji Android Studio oznacza walkę z niezrozumiałymi błędami. Trudno było także wspierać testy zrzutów ekranu, ponieważ biblioteka nie była zbyt stabilna i pojawiały się fałszywe alarmy. Testy zrzutów ekranu zostały usunięte z listy kontrolnej.

W rezultacie pozostało nam:

  • montaż ARK;
  • testy Junita;
  • Testy oprzyrządowania.

Zdalna pamięć podręczna Gradle

Bez ciężkich kontroli wszystko stało się lepsze. Ale nie ma ograniczeń co do doskonałości!

Nasza aplikacja została już podzielona na około 150 modułów stopniowych. Zdalna pamięć podręczna Gradle zwykle działa dobrze w tym przypadku, więc postanowiliśmy spróbować.

Zdalna pamięć podręczna Gradle to usługa, która może buforować artefakty kompilacji dla poszczególnych zadań w poszczególnych modułach. Gradle, zamiast faktycznie kompilować kod, używa protokołu HTTP, aby zapukać do zdalnej pamięci podręcznej i zapytać, czy ktoś już wykonał to zadanie. Jeśli tak, po prostu pobiera wynik.

Uruchamianie zdalnej pamięci podręcznej Gradle jest łatwe, ponieważ Gradle udostępnia obraz Dockera. Udało nam się to zrobić w trzy godziny.

Wystarczyło uruchomić Dockera i napisać jedną linię w projekcie. Ale chociaż można go szybko uruchomić, minie sporo czasu, zanim wszystko będzie dobrze działać.

Poniżej znajduje się wykres braków pamięci podręcznej.

Ewolucja CI w zespole programistów mobilnych

Na samym początku odsetek chybień w skrytce wynosił około 65. Po trzech tygodniach udało nam się zwiększyć tę wartość do 20%. Okazało się, że zadania zbierane przez aplikację na Androida mają dziwne zależności przechodnie, przez co Gradle przegapił pamięć podręczną.

Łącząc pamięć podręczną znacznie przyspieszyliśmy kompilację. Ale oprócz montażu są też testy oprzyrządowania, a one zajmują dużo czasu. Być może nie wszystkie testy muszą być uruchamiane dla każdego żądania ściągnięcia. Aby się tego dowiedzieć, korzystamy z analizy wpływu.

Analiza wpływu

Na żądanie ściągnięcia zbieramy git diff i znajdujemy zmodyfikowane moduły Gradle.

Ewolucja CI w zespole programistów mobilnych

Sensowne jest uruchamianie tylko testów oprzyrządowania, które sprawdzają zmienione moduły i wszystkie moduły od nich zależne. Nie ma sensu uruchamiać testów dla sąsiednich modułów: kod tam się nie zmienił i nic nie może się zepsuć.

W przypadku testów oprzyrządowania nie wszystko jest takie proste, gdyż muszą one znajdować się w module Aplikacji najwyższego poziomu. Użyliśmy heurystyki z analizą kodu bajtowego, aby zrozumieć, do którego modułu należy każdy test.

Modernizacja działania testów oprzyrządowania tak, aby testowały tylko zaangażowane moduły, zajęła około ośmiu tygodni.

Środki mające na celu przyspieszenie inspekcji okazały się skuteczne. Z 45 minut przeszliśmy do około 15. To już normalne, że trzeba czekać kwadrans na kompilację.

Ale teraz programiści zaczęli narzekać, że nie rozumieją, które kompilacje są uruchamiane, gdzie zobaczyć dziennik, dlaczego kompilacja jest czerwona, który test się nie powiódł itp.

Ewolucja CI w zespole programistów mobilnych

Problemy z informacją zwrotną spowalniają rozwój, dlatego staraliśmy się zapewnić jak najbardziej jasne i szczegółowe informacje o każdym PR i kompilacji. Zaczęliśmy od komentarzy w Bitbucket do PR, wskazując, która kompilacja się nie powiodła i dlaczego, a następnie pisaliśmy ukierunkowane wiadomości w Slacku. Na koniec stworzyliśmy dashboard PR dla strony z listą wszystkich aktualnie uruchomionych buildów i ich statusem: w kolejce, uruchomione, uległy awarii lub ukończone. Możesz kliknąć kompilację i przejść do jej dziennika.

Ewolucja CI w zespole programistów mobilnych

Na szczegółowe informacje zwrotne przeznaczono sześć tygodni.

Plany

Przejdźmy do historii najnowszej. Po rozwiązaniu problemu ze sprzężeniem zwrotnym osiągnęliśmy nowy poziom - postanowiliśmy zbudować własną farmę emulatorów. Kiedy jest wiele testów i emulatorów, trudno jest nimi zarządzać. W efekcie wszystkie nasze emulatory przeniosły się na klaster k8s z elastycznym zarządzaniem zasobami.

Poza tym są inne plany.

  • Zwróć Linta (i inna analiza statyczna). Już pracujemy w tym kierunku.
  • Uruchom wszystko na blokadzie PR testy typu end-to-end we wszystkich wersjach SDK.

Prześledziliśmy więc historię rozwoju Ciągłej Integracji w Avito. Teraz chcę dać kilka rad z doświadczonego punktu widzenia.

Советы

Gdybym mógł dać tylko jedną radę, brzmiałaby ona tak:

Proszę uważać na skrypty powłoki!

Bash jest bardzo elastycznym i potężnym narzędziem, bardzo wygodnie i szybko pisze się skrypty. Można jednak wpaść z nią w pułapkę i my niestety w nią wpadliśmy.

Wszystko zaczęło się od prostych skryptów, które działały na naszych komputerach:

#!/usr/bin/env bash
./gradlew assembleDebug

Ale jak wiadomo wszystko z czasem się rozwija i komplikuje - uruchommy jeden skrypt od drugiego, przekażmy tam jakieś parametry - na koniec trzeba było napisać funkcję, która określi na jakim poziomie zagnieżdżenia basha jesteśmy teraz w porządku wstawić niezbędne cudzysłowy i zacząć wszystko.

Ewolucja CI w zespole programistów mobilnych

Możesz sobie wyobrazić koszty pracy związane z opracowaniem takich skryptów. Radzę nie wpaść w tę pułapkę.

Co można wymienić?

  • Dowolny język skryptowy. Napisz do Skrypt w Pythonie lub Kotlinie wygodniejsze, ponieważ jest to programowanie, a nie skrypty.
  • Lub opisz całą logikę kompilacji w formularzu Niestandardowe zadania stopniowe dla Twojego projektu.

Zdecydowaliśmy się wybrać drugą opcję i teraz systematycznie usuwamy wszystkie skrypty basha i piszemy wiele niestandardowych zadań stopniowych.

Wskazówka nr 2: Przechowuj infrastrukturę w kodzie.

Jest to wygodne, gdy ustawienie Continuous Integration jest przechowywane nie w interfejsie UI Jenkinsa, TeamCity itp., ale w postaci plików tekstowych bezpośrednio w repozytorium projektu. Zapewnia to wersjonowalność. Przywrócenie lub zbudowanie kodu w innej gałęzi nie będzie trudne.

Skrypty można przechowywać w projekcie. Co zrobić ze środowiskiem?

Wskazówka nr 3: Docker może pomóc w ochronie środowiska.

Z pewnością pomoże programistom Androida; iOS jeszcze go nie ma, niestety.

Oto przykład prostego pliku dokowanego zawierającego jdk i Android-sdk:

FROM openjdk:8

ENV SDK_URL="https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip" 
    ANDROID_HOME="/usr/local/android-sdk" 
    ANDROID_VERSION=26 
    ANDROID_BUILD_TOOLS_VERSION=26.0.2

# Download Android SDK
RUN mkdir "$ANDROID_HOME" .android 
    && cd "$ANDROID_HOME" 
    && curl -o sdk.zip $SDK_URL 
    && unzip sdk.zip 
    && rm sdk.zip 
    && yes | $ANDROID_HOME/tools/bin/sdkmanager --licenses

# Install Android Build Tool and Libraries
RUN $ANDROID_HOME/tools/bin/sdkmanager --update
RUN $ANDROID_HOME/tools/bin/sdkmanager "build-tools;${ANDROID_BUILD_TOOLS_VERSION}" 
    "platforms;android-${ANDROID_VERSION}" 
    "platform-tools"

RUN mkdir /application
WORKDIR /application

Po napisaniu tego pliku Dockera (powiem Ci sekret, nie musisz go pisać, wystarczy pobrać go gotowego z GitHuba) i zmontować obraz, otrzymujesz maszynę wirtualną, na której możesz zbudować aplikację i uruchom testy Junit.

Dwa główne powody, dla których ma to sens, to skalowalność i powtarzalność. Za pomocą okna dokowanego możesz szybko utworzyć tuzin agentów kompilacji, które będą miały dokładnie takie samo środowisko jak poprzedni. To znacznie ułatwia życie inżynierom CI. Całkiem łatwo jest wepchnąć pakiet Android-sdk do okna dokowanego, ale w przypadku emulatorów jest to trochę trudniejsze: będziesz musiał popracować trochę ciężej (lub ponownie pobrać gotowy pakiet z GitHub).

Wskazówka nr 4: nie zapominajcie, że inspekcji nie robi się dla kontroli, ale dla ludzi.

Szybka i, co najważniejsze, jasna informacja zwrotna jest bardzo ważna dla programistów: co się zepsuło, jaki test się nie powiódł, gdzie mogę zobaczyć dziennik budowy.

Wskazówka nr 5: Bądź pragmatyczny podczas opracowywania ciągłej integracji.

Jasno zrozum, jakiego rodzaju błędom chcesz zapobiec, ile zasobów, czasu i czasu pracy komputera chcesz przeznaczyć. Kontrole, które trwają zbyt długo, można na przykład przełożyć na noc. A te z nich, które łapią niezbyt istotne błędy, należy całkowicie porzucić.

Wskazówka nr 6: Korzystaj z gotowych narzędzi.

Obecnie istnieje wiele firm oferujących rozwiązania CI w chmurze.

Ewolucja CI w zespole programistów mobilnych

To dobre rozwiązanie dla małych zespołów. Nie musisz niczego wspierać, po prostu zapłać trochę pieniędzy, zbuduj aplikację, a nawet przeprowadź testy oprzyrządowania.

Wskazówka nr 7: W dużym zespole bardziej opłacalne są rozwiązania własne.

Jednak prędzej czy później, w miarę powiększania się zespołu, rozwiązania własne staną się bardziej opłacalne. Jest jeden problem z tymi decyzjami. W ekonomii istnieje prawo malejących zysków: w każdym projekcie każde kolejne ulepszenie jest coraz trudniejsze i wymaga coraz większych inwestycji.

Ekonomia opisuje całe nasze życie, łącznie z Ciągłą Integracją. Zbudowałem harmonogram kosztów pracy dla każdego etapu rozwoju naszej Ciągłej Integracji.

Ewolucja CI w zespole programistów mobilnych

Oczywiste jest, że jakakolwiek poprawa staje się coraz trudniejsza. Patrząc na ten wykres, można zrozumieć, że ciągłą integrację należy rozwijać wraz ze wzrostem wielkości zespołu. Dla dwuosobowego zespołu spędzenie 50 dni na tworzeniu wewnętrznej farmy emulatorów to przeciętny pomysł. Ale jednocześnie dla dużego zespołu w ogóle nie robienie Continuous Integration jest również złym pomysłem, ponieważ problemy z integracją, problemy z komunikacją itp. zajmie to jeszcze więcej czasu.

Zaczęliśmy od pomysłu, że automatyzacja jest potrzebna, ponieważ ludzie są kosztowni, popełniają błędy i są leniwi. Ale ludzie też automatyzują. Dlatego te same problemy dotyczą automatyzacji.

  • Automatyzacja jest droga. Pamiętaj o harmonogramie pracy.
  • Jeśli chodzi o automatyzację, ludzie popełniają błędy.
  • Czasami automatyzacja jest bardzo leniwa, ponieważ wszystko działa w ten sposób. Po co ulepszać cokolwiek innego, po co ta cała ciągła integracja?

Ale mam statystyki: błędy są wychwytywane w 20% złożeń. I nie dzieje się tak dlatego, że nasi programiści źle piszą kod. Dzieje się tak dlatego, że programiści mają pewność, że jeśli popełnią jakiś błąd, nie trafi on do fazy deweloperskiej, lecz zostanie wykryty przez automatyczne kontrole. W związku z tym programiści mogą spędzić więcej czasu na pisaniu kodu i ciekawych rzeczy, zamiast uruchamiać i testować coś lokalnie.

Praktykuj ciągłą integrację. Ale z umiarem.

Nawiasem mówiąc, Nikołaj Niestierow nie tylko sam daje świetne raporty, ale także jest członkiem komitetu programowego Konf. aplikacji i pomaga innym w przygotowaniu dla Ciebie znaczących przemówień. Kompletność i użyteczność programu kolejnej konferencji można ocenić według tematów w harmonogram. Po szczegóły zapraszamy do Infospace w dniach 22-23 kwietnia.

Źródło: www.habr.com

Dodaj komentarz