Mit GitLab CI automatisch auf Staging- und Produktiv-Server deployen

Ich habe kürzlich angefangen, mich mit Continuous Integration (CI) zu beschäftigen. Damit sind unterschiedliche automatische Aktionen nach einem Push in ein Versionsverwaltungs-Repo möglich, wie etwa das Testen des Codes oder auch das Deployment auf einen Server. Hier zeige ich euch, wie ein Deployment-Prozess von Gitlab.com auf einen Staging- und Produktiv-Server aussehen kann.

Im Gegensatz zu GitHub hat GitLab die Möglichkeit zur CI direkt integriert, die sogar im kostenlosen Plan genutzt werden kann. Das Theme meiner Website wird mit Git versioniert. Es gibt eine lokale Version, eine auf einem Staging-Server und die hier auf dem Produktiv-Server. Über GitLab CI kann ich das Theme durch Pushen eines Commits automatisch auf den Staging- oder Produktiv-Server übertragen – je nachdem, ob der Push an den staging- oder master-Branch geht. So muss ich keine Dateien per SFTP auf unterschiedliche Server laden, sondern kann alles über Git regeln.

Um in einem kostenlosen GitLab-Account die CI zu nutzen, müsst ihr eigentlich nur eine .gitlab-ci.yml erstellen und auf die oberste Ebene des Git-Repos legen (bei einer eigenen GitLab-Instanz müsst ihr vielleicht erst Runner installieren). Darin steht dann, was GitLab CI machen soll, wenn ein Push ankommt. Wir möchten folgende Dinge umsetzen:

Bei einem Push im staging-Branch

  1. Verbinde dich mit dem Staging-Server.
  2. Erstelle einen temporären Theme-Ordner.
  3. Kopiere die Theme-Dateien aus dem Git-Repo in den temporären Ordner auf dem Staging-System.
  4. Verschiebe das bis dahin aktuelle Theme in einen weiteren temporären Ordner.
  5. Verschiebe den neuen aktuellen Stand in den Theme-Ordner.
  6. Lösche den temporären Ordner mit den alten Theme-Dateien.

Dieser Umweg über temporäre Ordner ist aus der GitLab-Dokumentation entnommen und sorgt dafür, dass immer ein funktionierendes Theme auf dem Server ist. Andernfalls wäre für eine kurze Zeit der Theme-Ordner nicht vorhanden.

Bei einem Push im master-Branch

Hier sieht der Vorgang gleich aus wie bei dem Staging-Server, nur dass sich mit dem Produktiv-Server verbunden wird.

Die .gitlab-ci.yml für das Deployment erstellen

In der .gitlab-ci.yml stehen wie bereits erwähnt die Schritte, die von der CI ausgeführt werden. Die Aufgaben können in unterschiedliche Jobs aufgeteilt werden, die in unterschiedlichen Stages laufen können. Wir haben nur eine deploy-Stage und die beiden Jobs deploy_staging und deploy_production. Der Anfang der Datei sieht so aus:

stages: - deploy deploy_staging: stage: deploy image: tetraweb/php:7.1 before_script: - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )' - mkdir -p ~/.ssh - eval $(ssh-agent -s) - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config' - ssh-add <(echo "$STAGING_PRIVATE_KEY") - apt-get install rsync script: - ssh -p22 user@staging.example.com "mkdir -p /html/wp-content/themes/_tmp" - rsync -rav -e ssh --exclude='.git/' --exclude='.gitlab-ci.yml' --delete-excluded ./ user@staging.example.com:/html/wp-content/themes/_tmp - ssh -p22 user@staging.example.com "mv /html/wp-content/themes/fbn /html/wp-content/themes/_old && mv /html/wp-content/themes/_tmp /html/wp-content/themes/fbn" - ssh -p22 user@staging.example.com "rm -rf /html/wp-content/themes/_old" only: - staging
Code-Sprache: YAML (yaml)

Zunächst wird die deploy-Stage definiert und anschließend der Job deploy_staging, der sich um das Deployment auf den Staging-Server kümmert. Dieser Job gehört zu deploy und wir nutzen zum Ausführen das Docker-Image tetraweb/php:7.1. Über before_script können wir Aufgaben vor Durchführung des Skripts ausführen – in unserem Fall die Vorbereitung für eine SSH-Verbindung und die Installation des rsync-Pakets. Die Schritte für SSH sind wieder aus der GitLab-Doku. Damit es funktioniert, müsst ihr in GitLab eine Variable STAGING_PRIVATE_KEY anlegen, die den privaten Key des SSH-Schlüssels enthält.

Im script-Teil des Jobs verbinden wir uns per SSH mit dem Staging-Server und erstellen den Ordner _tmp im Theme-Verzeichnis, falls noch nicht vorhanden. Danach übertragen wir per rsync die Daten aus dem Git-Repo in diesen Ordner. Im Anschluss wird das Verschieben der Ordner vorgenommen. Über only können wir einen Branch angeben, für den dieser Job gelten soll – andernfalls würde der Job bei jedem Push ausgeführt, da die Datei ja auch im master-Branch liegt.

Letztlich sieht der Job für das Deployment zum Produktiv-Server fast genauso aus (der Code wird einfach unter dem anderen Job eingefügt):

deploy_production: stage: deploy image: tetraweb/php:7.1 before_script: - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )' - mkdir -p ~/.ssh - eval $(ssh-agent -s) - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config' - ssh-add <(echo "$STAGING_PRIVATE_KEY") - apt-get install rsync script: - ssh -p22 user@example.com "mkdir -p /html/wp-content/themes/_tmp" - rsync -rav -e ssh --exclude='.git/' --exclude='.gitlab-ci.yml' --delete-excluded ./ user@example.com:/html/wp-content/themes/_tmp - ssh -p22 user@example.com "mv /html/wp-content/themes/fbn /html/wp-content/themes/_old && mv /html/wp-content/themes/_tmp /html/wp-content/themes/fbn" - ssh -p22 user@example.com "rm -rf /html/wp-content/themes/_old" only: - master
Code-Sprache: YAML (yaml)

Da der before_script-Teil in diesem Beispiel für beide Jobs genau gleich ist, könnte er auch aus den Jobs rausgezogen und global eingefügt werden (auf dieselbe Ebene wie die Angabe von stages).

Und damit wären wir auch schon am Ende und können in Zukunft das Deployment mit Git-Aktionen vornehmen.

6 Reaktionen zu »Mit GitLab CI automatisch auf Staging- und Produktiv-Server deployen«

  1. Schöne Anleitung. Ich hätte nur einen Verbesserungsvorschlag und einen Hinweis. Statt bei jedem Deployment den Container immer neu zu bauen, würde ich einen vorbereiten, bei dem rsync schon installiert ist. Da GitLab ja auch eine eigene Docker-Registry hat, kannst du diese auch direkt für die Speicherung dieses Containers nutzen.

    Und noch effektiver als ein Deployment mit rsync ist eines per Git, sofern auf dem Server vorhanden. Meine .gitlab-ci.yml sieht in etwa wie folgt aus:

    stages:
    - deploy
    variables:
      # Setting some global variable used later in some script.
      # Values can be overwritten in individual jobs.
      SSH_HOST: [user]@[www.example.com]
      SSH_DOCUMENT_ROOT: /path/to/document/root
    .ssh_git_deployment: &ssh_git_deployment
      image: gitlab-registry.example.com/gitlab/gitlab-ci-git-deploy
      stage: deploy
      script:
      - ssh $SSH_HOST "cd $SSH_DOCUMENT_ROOT && git fetch && git checkout $CI_COMMIT_SHA && git submodule foreach git fetch --tags && git submodule update"
    deploy:staging:
      <<: *ssh_git_deployment
      environment: staging
      # This is an example of an overwritten variable.
      # The other variable SSH_HOST will be used as defined globally.
      variables:
        SSH_DOCUMENT_ROOT: ~/domains/staging.example.com/public_html
      only:
      - master
    deploy:production:
      <<: *ssh_git_deployment
      environment: production
      only:
      - tags
      when: manual

    Das Deployment passiert hier aus Staging immer bei einem Push auf master, der immer stabil ist. Ein Push auf Production wird manuell über GitLab ausgeführt und ist nur auf Tags zugelassen. Auch die Rollbacks sind hierdurch super einfach möglich.

    Der Image gitlab-ci-git-deploy beinhaltet schon alles, was notwendig ist. Lediglich der Public-Key muss dann noch auf dem Zielsystem für den Nutzer in die Datei ~/.ssh/authorized_keys eingetragen werden.

    Aber wenn es mal kein Git auf dem Zielsystem gibt, ist deine Anleitung wirklich sehr hilfreich. Werde ich mal ausprobieren, denn mit Git-FTP kommt es genau zu dem beschriebenen Problem, dass nicht immer alle Dateien auf einmal übertragen werden können.

  2. Danke für den Beitrag. Ich hab gerade mit deinem Post meine erste Pipeline auf Gitlab CI erstellt. Auch wenn ich Bernhards Methode bevorzuge, git zu verwenden, war deine Anleitung erstmal einfacher zu verstehen und nach zu bauen. Als nächstes werde ich mich mal mit Bernhards Lösung beschäftigen und das ganze dann auf einen git-deploy modus umstellen.

  3. Kann man nicht einfach den Gitlab-Runner auf dem Produktiv-Server laufen lassen? Dann braucht man ja kein ssh mehr, um zu deployen sondern kann lokal arbeiten

    1. Hey,

      danke für Deinen Kommentar und den guten Tipp! Ich wusste bis eben zwar nicht, dass man den Runner so abgekoppelt von einer GitLab-Instanz laufen lassen kann, aber ja, das wäre noch eine weitere Möglichkeit 🙂

      Viele Grüße
      Florian

  4. Das hätte nur den Nachteil, dass die Performance & Security des Produktiv-Servers drunter leidet, wenn da ein Runner drauf läuft.
    Vll. ist ssh doch besser.

Erwähnungen

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert