niedziela, 3 czerwca 2018

TeamCity - migracja do kontenera dockerowego

Niniejsza pozycja omawia przejście z serwera CI CD TeamCity w wersji 9.1.7 do TeamCity w wersji 2017.2.3 umieszczonego w kontenerze dockerowym pod kontrolą Docker Swarm. Bazą danych, którą wykorzystano w przykładzie jest MySQL.

Pierwszym krokiem, który musimy jest wykonanie kopii zapasowej TeamCity:
  • prawy górny róg i "Administration",
  • belka po lewej i "Backup",
  • z listy rozwijanej "Backup scope" określamy co ma zawierać kopia, a wybranie opcji "Custom" pozwala nam na indywidualne określenie zakresu,
  • możemy określić nazwę pliku w sekcji "Backup file"
  • pole "add timestamp suffix" doda nam w nazwie pliku kopii zapasowej wzorzec daty i czasu,
  • naciskamy "Start Backup".
Kopię można również wykonać za pomocą dedykowanego narzędzia znajdującego się w katalogu z instalacją TC. Dla przykładu w moim wypadku będzie to wywołanie "/home/teamcity/teamcity/bin/maintainDB.sh -C -D -L -P".

Oczekujemy na zakończenie wykonywania się kopii zapasowej (będzie miała rozszerzenie "zip"). W podsumowaniu znajduje się ścieżka do pliku.

W związku z tym, że kopia nie obejmuje artefaktów to musimy je skopiować sami jeżeli chcemy je również mieć w nowej instancji TC.

Udajemy się do katalogu z artefaktami, który jest zależy od tego gdzie jest zainstalowane TC. W moim przypadku jest to "/home/teamcity/.BuildServer/system/artifacts" i wydajemy tam komendę
tar -czvf artifacts.tar.gz .
Przenosimy archiwa z kopią zapasową i artefaktami na serwer docelowy gdzie będzie stało nasze TeamCity w kontenerze.

Teraz przyszła pora na wdrożenie naszego nowego TC na docelowej maszynie. Ja zrobię to przy pomocy Ansible'a uruchamiają plik Docker Compose'a stworzony z szablonu Ansible, który ma następującą postać:
version: '3.5' 
services:
  mysql:
    image: mysql:5.7
    ports:
      - 3306:3306
    networks:
      - teamcity
    environment:
      MYSQL_ROOT_PASSWORD: {{ vault_mysql_root_password }}
      MYSQL_DATABASE: "{{ mysql_teamcity_database }}"
      MYSQL_USER: "{{ mysql_teamcity_user }}"
      MYSQL_PASSWORD: "{{ vault_mysql_teamcity_user_password }}"
    volumes:
      - mysql_data_directory:/var/lib/mysql
    deploy:
      replicas: 1
      placement:
        constraints: [node.role == manager]
      restart_policy:
        condition: on-failure
 
  teamcity:
    image: jetbrains/teamcity-server:2017.2.3
    ports:
      - 8111:8111
    networks:
      - teamcity
      - traefik
    volumes:
      - teamcity_data_directory:/data/teamcity_server/datadir
      - teamcity_logs_directory:/opt/teamcity/logs
    deploy:
      replicas: 1
      placement:
        constraints: [node.role == manager]
      restart_policy:
        condition: on-failure
      labels:
        traefik.frontend.entryPoints: "http,https"
        traefik.frontend.rule: "Host:${HOST}"
        traefik.http.port: 8111
        traefik.backend:
 
volumes:
  mysql_data_directory:
  teamcity_data_directory:
  teamcity_logs_directory:
 
networks:
  teamcity:
    driver: overlay
    attachable: true
  traefik:
    external:
      name: traefik
A więc na docelowym serwerze mamy świeże i niezainicjalizowane TC w kontenerze dockerowym w Swarmie. Kolejnym krokiem jest uruchomienie dodatkowego kontenera z TeamCity współdzielącego wolumen, w którym dokonamy przywrócenia danych:
bkorpala@hercules:~$ sudo docker run -it --name restore-tc --rm --network=teamcity_teamcity --volumes-from=teamcity_teamcity.1.41rl6y4npfcb7pj7odvs4pfvo jetbrains/teamcity-server /bin/bash
   Welcome to TeamCity Server Docker container
 * Installation directory: /opt/teamcity
 * Logs directory:         /opt/teamcity/logs
 * Data directory:         /data/teamcity_server/datadir
root@73b7919e46dc:/#
Jako argument opcji "--network" podajemy sieć która została utworzona dla TC przez Swarma ("sudo docker network ls"), a dla opcji "--volumes-from" podajemy nazwę kontenera TC ("sudo docker ps").

Jeżeli chcemy wyjść z kontenera bez zabicia go to wciskamy najpierw klawisz Ctrl + P, a potem Ctrl + Q. Aby wejść znów do kontenera wpisujemy "sudo docker exec -it restore-tc /bin/bash".

Z pierwotnej instancji TC na serwer docelowy kopiujemy informację na temat połączenia z bazą danych. W moim przypadku jest to "/home/teamcity/.BuildServer/config/database.properties".

Otwieramy plik i modyfikujemy go wpisując użytkownika, hasło oraz nazwę bazy, które użyjemy w nowej instancji. Mój plik np. wygląda następująco:
# Database: HSQLDB (HyperSonic) version 2.x
connectionUrl=jdbc:mysql://mysql:3306/teamcity?useUnicode=yes&characterEncoding=UTF-8
connectionProperties.user=teamcity
connectionProperties.password=MGjHHVgm7ABSvnbP
Dzięki temu, że jesteśmy w zadeklarowanej przez nas sieci dockerowej to dla serwera MySQL możemy użyć nazwy domenowej, która odpowiada nazwie usługi w Docker Compose.

Do kontenera z dodatkowym TC kopiujemy plik z informacją na temat połączenia z bazą danych:
sudo docker cp database.properties restore-tc:/restore-database.properties
Kopiujemy tam również kopię bazy danych:
sudo docker cp TeamCity_Backup_Before_Update_20180601_200257.zip restore-tc:/backup.zip
 Następnie musimy pobrać sterownik dla połączeń z bazą danych MySQL ("https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-8.0.11.tar.gz") na serwer docelowy i go rozpakować. W kontenerze z dodatkowym TC tworzymy strukturę katalogów "/data/teamcity_server/datadir/lib/jdbc" i kopiujemy do niego nasz sterownik:
sudo docker cp mysql-connector-java-8.0.11/mysql-connector-java-8.0.11.jar restore-tc:/data/teamcity_server/datadir/lib/jdbc/
W kontenerze z dodatkowym TC wydajemy komendę, która zacznie proces odzyskiwania:
/opt/teamcity/bin/maintainDB.sh restore -A /data/teamcity_server/datadir/ -F /backup.zip -T /restore-database.properties
Po wgraniu kopii musimy jeszcze wgrać artefakty. Dla przykładu w moim wypadku (artefakty są umieszczone w folderach odpowiadających nazwom projektów) znajdują się one w katalogu "artifacts" po wypakowaniu archiwum, które wcześniej utworzyliśmy:
 sudo docker cp artifacts/. restore-tc:/data/teamcity_server/datadir/system/artifacts/
Uruchamiamy ponownie kontener z naszym nowym TC. W moim przypadku będzie to restart usługi Docker Swarma:
bkorpala@hercules:~$ sudo docker service scale teamcity_teamcity=0
teamcity_teamcity scaled to 0
overall progress: 0 out of 0 tasks
verify: Service converged
bkorpala@hercules:~$ sudo docker service scale teamcity_teamcity=1
teamcity_teamcity scaled to 1
overall progress: 1 out of 1 tasks
1/1: running   [==================================================>]
verify: Service converged
Wchodzimy na stronę z nowym TC i widzimy informację "TeamCity server requires technical maintenance. Please let the server administrator know this." i wtedy klikamy na "I'm a server administrator, show me the details". Musimy podać kod autoryzacyjny, który uzyskamy wchodząc do kontenera i odczytując logi jak np.:
bkorpala@hercules:~$ sudo docker exec -it teamcity_teamcity.1.foie75s0t5bk8grqz6jqm5xjw tail /opt/teamcity/logs/teamcity-server.log
[2018-06-03 04:04:46,377]   INFO -  jetbrains.buildServer.STARTUP - The database properties file "/data/teamcity_server/datadir/config/database.properties" doesn't exist
[2018-06-03 04:04:46,377]   INFO -  jetbrains.buildServer.STARTUP - The internal database data file "/data/teamcity_server/datadir/system/buildserver.data" doesn't exist
[2018-06-03 04:04:46,393]   INFO -  jetbrains.buildServer.STARTUP - Data Directory version: 727
[2018-06-03 04:04:46,393]   INFO -  jetbrains.buildServer.STARTUP - Current stage: Looking for the database configuration
[2018-06-03 04:04:46,393]   INFO -  jetbrains.buildServer.STARTUP - Database properties file "/data/teamcity_server/datadir/config/database.properties" doesn't exist
[2018-06-03 04:04:46,393]   INFO -  jetbrains.buildServer.STARTUP - Internal HSQL database file (/data/teamcity_server/datadir/system/buildserver.data) doesn't exist
[2018-06-03 04:04:46,393]   INFO -  jetbrains.buildServer.STARTUP - Neither database properties file nor internal database found.
[2018-06-03 04:04:46,393]  ERROR -  jetbrains.buildServer.STARTUP - Data parts are inconsistent: the Data Directory exists (from another version of TeamCity) but the database does not.
[2018-06-03 04:04:46,394]   INFO -  jetbrains.buildServer.STARTUP - Current stage: TeamCity server startup error
[2018-06-03 04:04:46,394]   INFO -  jetbrains.buildServer.STARTUP - Administrator can login from web UI using authentication token: 8288277720538082065
Widzimy, że brakuje nam plik z informacją na temat połączenia do bazy "/data/teamcity_server/datadir/config/database.properties" więc musimy go skopiować do kontenera i jeszcze raz zrestartować usługę w Docker Swarmie.

Kolejnym krokiem jest kliknięcie na "Confirm", a następnie "Upgrade". Teraz oczekujemy aż aktualizacja się zakończy.