One place for hosting & domains

      mithilfe

      Bereitstellen von Laravel 7 und MySQL in Kubernetes mithilfe von Helm


      Der Autor hat den Diversity in Tech Fund dazu ausgewählt, eine Spende im Rahmen des Programms Write for DOnations zu erhalten.

      Einführung

      Laravel ist heute eines der beliebtesten Open-Source-basierten PHP-Anwendungsframeworks. Es wird oft mit einer MySQL-Datenbank bereitgestellt, kann aber so konfiguriert werden, dass verschiedene Backend-Datenspeicheroptionen zum Einsatz kommen. Laravel ist stolz darauf, viele moderne Funktionen und das umfangreiche Paketökosystem von PHP zu nutzen.

      Kubernetes ist eine Plattform zur Orchestrierung von Containern, die in DigitalOcean Kubernetes-Clustern gehostet werden kann, um einen Großteil der Verwaltungsaufgaben bei der Einrichtung und Ausführung von Containern in der Produktion zu übernehmen. Helm ist ein Kubernetes-Paketmanager, der das Konfigurieren und Installieren von Diensten und Pods vereinfacht.

      In diesem Leitfaden erstellen Sie eine Laravel PHP-Anwendung, erstellen Ihre App in einem Docker-Image und stellen das Image mithilfe des LAMP Helm Chart in einem DigitalOcean Kubernetes-Cluster bereit. Als Nächstes richten Sie einen Ingress Controller ein, um Ihrer App SSL und einen benutzerdefinierten Domänennamen hinzuzufügen.Danach verfügen Sie eine funktionierende Laravel-Anwendung, die mit einer MySQL-Datenbank verbunden ist, die in einem Kubernetes-Cluster ausgeführt wird.

      Voraussetzungen

      • Docker, installiert auf dem Computer, von dem aus Sie auf Ihren Cluster zugreifen werden. Detaillierte Anweisungen zum Installieren von Docker für die meisten Linux-Distributionen finden Sie hier oder für andere Betriebssysteme auf der Website von Docker.
      • Ein Konto bei Docker Hub zur Speicherung von Docker-Images, die Sie in diesem Tutorial erstellen werden.
      • Einen DigitalOcean Kubernetes 1.17+-Cluster, bei dem Ihre Verbindung als der kubectl-Standard konfiguriert ist. Um zu erfahren, wie Sie einen Kubernetes-Cluster in DigitalOcean erstellen können, lesen Sie unser Dokument Kubernetes Schnellstart. Um zu erfahren, wie Sie eine Verbindung zum Cluster herstellen können, konsultieren Sie Herstellen einer Verbindung zu einem DigitalOcean Kubernetes-Cluster.
      • Helm 3-Paketmanager, auf Ihrem lokalen Rechner installiert. Führen Sie den ersten Schritt aus und fügen Sie das stable-Repository aus dem zweiten Schritt des Tutorials Installieren von Software in Kubernetes-Clustern mit dem Helm 3-Paketmanager hinzu.
      • Einen vollständig registrierten Domänennamen mit einem verfügbaren A-Eintrag. Dieses Tutorial verwendet in allen Bereichen your_domain. Sie können einen Domänennamen unter Namecheap günstig erwerben oder einen kostenlosen von Freenom herunterladen oder einfach die Domänenregistrierungsstelle Ihrer Wahl verwenden. Sie müssen sich erst einmal keine Gedanken um eine Verknüpfung des A-Eintrags Ihrer Domäne mit einer IP-Adresse machen. Sobald Sie Schritt 5 erreichen und Ihr Ingress Controller verfügbar ist, werden Sie your_domain mit der richtigen IP-Adresse verbinden.

      Schritt 1 — Erstellen einer neuen Laravel-Anwendung

      In diesem Schritt verwenden Sie Docker, um eine neue Laravel-7-Anwendung zu erstellen. Sie sollten jedoch mit einer bestehenden Laravel-Anwendung, die MySQL als Backing-Datenbank nutzt, das gleiche Verfahren nutzen können. Die neu erstellte Anwendung wird überprüfen, ob Laravel mit der Datenbank verbunden ist, und den Namen der Datenbank anzeigen.

      Wechseln Sie zunächst in Ihr Stammverzeichnis und erstellen Sie dann eine neue Laravel-Anwendung mit einem Docker-Container vom Typ composer:

      • cd ~
      • docker run --rm -v $(pwd):/app composer create-project --prefer-dist laravel/laravel laravel-kubernetes

      Nach der Fertigstellung des Containers und der Installation aller Composer-Pakete sollten Sie eine neue Installation von Laravel in Ihrem aktuellen Verzeichnis namens laravel-kubernetes/ sehen. Navigieren Sie zu diesem Ordner:

      Von hier führen Sie die restlichen Befehle dieses Tutorials aus.

      Zweck dieser Anwendung ist es, Ihre Datenbankverbindung zu testen und den Namen der Datenbank in Ihrem Browser anzuzeigen. Öffnen Sie die Datei ./resources/views/welcome.blade.php in einem Texteditor, um die Datenbankverbindung zu testen:

      • nano ./resources/views/welcome.blade.php

      Suchen Sie nach dem Abschnitt <div class="links">...</div> und ersetzen Sie den Inhalt durch Folgendes:

      ./resources/views/welcome.blade.php

      ...
      <div class="links">
         <strong>Database Connected: </strong>
          @php
              try {
                  DB::connection()->getPDO();
                  echo DB::connection()->getDatabaseName();
                  } catch (Exception $e) {
                  echo 'None';
              }
          @endphp
      </div>
      ...
      

      Speichern und schließen Sie die Datei.

      Weitere Anpassungen müssen Sie in diesem Tutorial an der standardmäßigen Laravel-Anwendung nicht vornehmen. Nun wird dieser kurze PHP-Abschnitt Ihre Datenbankverbindung testen und den Namen der Datenbank im Laravel-Begrüßungsbildschirm in Ihrem Webbrowser anzeigen.

      Im nächsten Schritt verwenden Sie Docker, um ein Image zu erstellen, das diese Laravel-Anwendung und Docker Compose enthält, um zu testen, ob sie lokal ausgeführt wird und eine Verbindung zu einer MySQL-Datenbank hergestellt wird.

      Schritt 2 — Containerisieren Ihrer Laravel-Anwendung

      Nachdem Sie eine neue Laravel-Anwendung erzeugt haben, müssen Sie nun Ihren Code in ein Docker-Image integrieren und das Image dann mit Docker Compose testen. Zwar ist das Ziel dieses Tutorials, Ihre Anwendung in einem Kubernetes-Cluster bereitzustellen, doch ist Docker Compose eine praktische Option, um Ihr Docker-Image und Ihre Konfiguration vor Ort zu testen, bevor Sie sie in der Cloud ausführen. Die schnelle Feedbackschleife kann nützlich sein, um kleine Änderungen vorzunehmen und zu testen.

      Erstellen Sie zunächst mit nano oder Ihrem bevorzugten Texteditor im Stammverzeichnis Ihrer Laravel-Anwendung eine Datei namens Dockerfile:

      Fügen Sie folgenden Inhalt hinzu: Docker wird diese Datei verwenden, um Ihren Code in ein Image zu integrieren:

      ./Dockerfile

      FROM php:7.4-apache
      
      # Install packages
      RUN apt-get update && apt-get install -y 
          git 
          zip 
          curl 
          sudo 
          unzip 
          libicu-dev 
          libbz2-dev 
          libpng-dev 
          libjpeg-dev 
          libmcrypt-dev 
          libreadline-dev 
          libfreetype6-dev 
          g++
      
      # Apache configuration
      ENV APACHE_DOCUMENT_ROOT=/var/www/html/public
      RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf
      RUN sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf
      RUN a2enmod rewrite headers
      
      # Common PHP Extensions
      RUN docker-php-ext-install 
          bz2 
          intl 
          iconv 
          bcmath 
          opcache 
          calendar 
          pdo_mysql
      
      # Ensure PHP logs are captured by the container
      ENV LOG_CHANNEL=stderr
      
      # Set a volume mount point for your code
      VOLUME /var/www/html
      
      # Copy code and run composer
      COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
      COPY . /var/www/tmp
      RUN cd /var/www/tmp && composer install --no-dev
      
      # Ensure the entrypoint file can be run
      RUN chmod +x /var/www/tmp/docker-entrypoint.sh
      ENTRYPOINT ["/var/www/tmp/docker-entrypoint.sh"]
      
      # The default apache run command
      CMD ["apache2-foreground"]
      

      Speichern und schließen Sie die Datei.

      Diese Dockerfile-Datei startet mit dem in Docker Hub gefundenen PHP 7.4 Apache Docker-Image und installiert dann mehrere Linux-Pakete, die allgemein von Laravel-Anwendungen benötigt werden. Als Nächstes erstellt sie Apache-Konfigurationsdateien und ermöglicht das Umschreiben von Headern. Die Dockerfile-Datei installiert mehrere gängige PHP-Erweiterungen und fügt eine Umgebungsvariable hinzu, um sicherzustellen, dass die Protokolle von Laravel über stderr an den Container gestreamt werden. So können Sie Laravel-Protokolle sehen, indem Sie Ihre Docker Compose- oder Kubernetes-Protokolle durchsehen.

      Schließlich kopiert die Dockerfile-Datei den gesamten Code in Ihrer Laravel-Anwendung nach /var/www/tmp und installiert die Abhängigkeiten von Composer. Dann setzt sie einen ENTRYPOINT. Sie müssen diese Datei aber noch erstellen, was wir als Nächstes tun werden.

      Erstellen Sie im Stammverzeichnis Ihres Projekts eine neue Datei namens docker-entrypoint.sh. Diese Datei wird ausgeführt, wenn Ihr Container lokal oder im Kubernetes Cluster ausgeführt wird. Außerdem wird Ihr Laravel-Anwendungscode vom Verzeichnis /var/www/tmp in /var/www/html kopiert, wo Apache ihn bereitstellen kann.

      • nano ./docker-entrypoint.sh

      Fügen Sie nun folgendes Skript hinzu:

      ./docker-entrypoint.sh

      #!/bin/bash
      
      cp -R /var/www/tmp/. /var/www/html/
      chown -R www-data:www-data /var/www/html
      
      exec "$@"
      

      Die abschließende Zeile exec "$@" weist das Shell an, jeden Befehl auszuführen, der als Nächstes als Eingabeargumenttext übergeben wurde. Dies ist wichtig, da Docker nach Ausführung dieses Skripts den Apache run-Befehl (apache2-foreground) weiter ausführen soll. Speichern und schließen Sie die Datei.

      Erstellen Sie als Nächstes im Stammverzeichnis Ihrer Anwendung eine Datei namens .dockerignore. Diese Datei sorgt dafür, dass Ihr Docker-Image beim Erstellen nicht mit Paketen oder Umgebungsdateien verschmutzt wird, die nicht hinein kopiert werden sollen:

      ./.dockerignore

      .env
      /vendor
      

      Speichern und schließen Sie die Datei.

      Die letzte Datei, die Sie erstellen müssen, bevor Sie Ihre Anwendung mit Docker Compose lokal ausführen können, ist eine docker-compose.yml-Datei. Bei der Konfiguration dieser YAML-Datei müssen Sie jedoch den APP_KEY eingeben, den Laravel bei der Installation generiert hat. Um ihn zu finden, öffnen und durchsuchen Sie die Datei . /.env oder führen die Sie die folgenden Befehle cat und grep aus:

      Sie werden eine Ausgabe wie diese sehen:

      Output

      APP_KEY=base64:0EHhVpgg ... UjGE=

      Kopieren Sie Ihren Schlüssel in die Zwischenablage. Vergewissern Sie sich, dass Sie das Präfix base64: einschließen. Erstellen Sie nun im Stammverzeichnis Ihrer Anwendung die Datei namens docker-compose.yml:

      • nano ./docker-compose.yml

      Hier werden wir das PHP-Image Ihrer Laravel-Anwendung sowie einen MySQL-Container für die Ausführung Ihrer Datenbank einschließen. Fügen Sie den folgenden Inhalt hinzu:

      ./docker-compose.yml

      version: '3.5'
      services:
        php:
          image: your_docker_hub_username/laravel-kubernetes:latest
          restart: always
          ports:
            - 8000:80
          environment:
            - APP_KEY="your_laravel_app_key"
            - APP_ENV=local
            - APP_DEBUG=true
            - DB_PORT=3306
            - DB_HOST=mysql
            - DB_DATABASE
            - DB_USERNAME
            - DB_PASSWORD
        mysql:
          image: mysql:5.7
          restart: always
          environment:
            - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
            - MYSQL_DATABASE=${DB_DATABASE}
            - MYSQL_USER=${DB_USERNAME}
            - MYSQL_PASSWORD=${DB_PASSWORD}
      

      Verwenden Sie die Variable APP_KEY, die Sie in Ihre Zwischenablage kopiert haben, für die Variable <^>your_laravel_app_key und Ihren Docker Hub-Benutzernamen für die Variable your_docker_hub_username<^>. Speichern und schließen Sie die Datei.

      Sie werden das erste Image lokal mit docker build erstellen. Das zweite Image ist das offizielle MySQL Docker-Image, das in Docker Hub verfügbar ist. Beide benötigen verschiedene Umgebungsvariablen, die Sie bei Ausführung der Container einschließen werden.

      Um das Docker-Image mit Ihrer Laravel-Anwendung zu erstellen, führen Sie folgenden Befehl aus. Ersetzen Sie your_docker_hub_username durch Ihren Benutzernamen oder den Benutzernamen Ihres Teams bei Docker Hub, wo dieses Image gespeichert werden soll:

      • docker build -t your_docker_hub_username/laravel-kubernetes:latest .

      Als Nächstes können Sie die beiden Container unter Verwendung von Docker Compose mit den erforderlichen Datenbankanmeldedaten ausführen:

      • DB_ROOT_PASSWORD=rootpassword DB_DATABASE=local_db DB_USERNAME=admin DB_PASSWORD=password docker-compose up -d

      Die hier verwendeten vier Umgebungsvariablen (DB_ROOT_PASSWORD, DB_DATABASE, DB_USERNAME, DB_PASSWORD) können bei Bedarf geändert werden; da Sie Ihre Anwendung jedoch nur vor Ort testen, müssen Sie sich noch nicht um ihre Sicherheit kümmern.

      Es kann bis zu 30 Sekunden dauern, bis Ihre MySQL-Datenbank initialisiert ist und die Container einsatzbereit sind. Sobald das der Fall ist, können Sie Ihre Laravel-Anwendung auf Ihrem Computer unter localhost:8000 anzeigen.

      Die Laravel-Anwendung, die mit Docker Compose lokal ausgeführt wird

      Ihre PHP-Anwendung wird sich mit Ihrer MySQL-Datenbank verbinden. Nach erfolgreicher Verbindungsherstellung wird unter dem Laravel-Logo der Text „Database Connected: localdb“ (Datenbank verbunden: localdb) angezeigt.

      Nachdem Sie Ihr Docker-Image mit Docker Compose lokal getestet haben, können Sie die Container mit docker-compose down nun herunterfahren:

      Im nächsten Abschnitt pushen Sie Ihr Docker-Image an Docker Hub, damit es Ihr Helm Chart nutzen kann, um die Anwendung in Ihrem Kubernetes-Cluster bereitzustellen.

      Schritt 3 — Pushen Ihres Docker-Image an Docker Hub

      Das LAMP Helm Chart, das Sie zur Bereitstellung Ihres Codes an Kubernetes verwenden werden, erfordert, dass Ihr Code in einer Container-Registry verfügbar ist. Zwar können Sie Ihr Image in eine private oder selbst gehostete Registry pushen, doch verwenden Sie in diesem Tutorial eine öffentlich verfügbare und kostenlose Docker-Registry in Docker Hub.

      Greifen Sie mit Ihrem Webbrowser auf Ihr Konto in Docker Hub zu und erstellen Sie dann ein neues Repository namens laravel-kubernetes.

      Erstellen eines neuen Repository in Docker Hub

      Wenn Sie von Ihrem lokalen Computer noch keine Verbindung zu Docker Hub hergestellt haben, müssen Sie sich bei Docker Hub anmelden. Sie können dies über die Befehlszeile tun:

      • docker login -u your_docker_hub_username

      Geben Sie Ihre Anmeldedaten ein, wenn Sie dazu aufgefordert werden. Dies muss normalerweise nur einmal pro Computer erfolgen, da Docker Ihre Anmeldedaten in Ihrem Stammverzeichnis in ~/.docker/config.json speichert.

      Abschließend pushen Sie Ihr Image an Docker Hub:

      • docker push your_docker_hub_username/laravel-kubernetes:latest

      Je nach Verbindungsgeschwindigkeit kann es einige Minuten dauern, bis Ihre Anwendung hochgeladen ist. Sobald Docker fertig ist, sehen Sie ein endgültiges Digest-Hash und die Größe Ihres Images im Terminal. Dies sollte ungefähr so aussehen:

      Output

      latest: digest: sha256:df4bdeda91484c8c26a989b13b8f27ab14d93ab2e676e3c396714cb3811c4086 size: 4918

      Nachdem Sie Ihre Laravel-Anwendung containerisiert und ein Image an Docker Hub gepusht haben, können Sie das Image nun in einer Helm Chart- oder Kubernetes-Bereitstellung verwenden. Im nächsten Schritt werden Sie basierend auf dem LAMP Helm Chart benutzerdefinierte Werte festlegen und die Anwendung in Ihrem DigitalOcean Kubernetes-Cluster bereitstellen.

      Schritt 4 — Konfigurieren und Bereitstellen der Anwendung mit dem LAMP Helm Chart

      Helm bietet eine Reihe von Charts, um Ihnen mit voreingestellten Kombinationen von Tools bei der Einrichtung von Kubernetes-Anwendungen zu helfen. Sie können zwar eigene Kubernetes-Dienstdateien schreiben, um eine eine ähnliche Bereitstellung zu erhalten, doch werden Sie in diesem Bereich sehen, warum die Verwendung eines Helm Chart die Konfiguration deutlich erleichtert.

      Zuerst benötigen Sie ein Verzeichnis, in dem alle Ihre Helm-Konfigurationsdateien gespeichert werden. Erstellen Sie im Stammverzeichnis Ihres Laravel-Projekts ein neues Verzeichnis namens helm/:

      Im Verzeichnis helm/ werden Sie zwei neue Dateien erstellen: values.yml und secrets.yml. Erstellen und öffnen Sie zunächst values.yml:

      Die Datei values.yml enthält nicht-geheime Konfigurationsoptionen, die die Standardwerte im LAMP Helm Chart überschreiben werden. Fügen Sie folgende Konfigurationen hinzu und stellen Sie sicher, your_docker_hub_username durch Ihren eigenen Benutzernamen zu ersetzen:

      ./helm/values.yml

      php:
        repository: "your_docker_hub_username/laravel-kubernetes"
        tag: "latest"
        fpmEnabled: false
        envVars:
          - name: APP_ENV
            value: production
          - name: APP_DEBUG
            value: false
          - name: DB_PORT
            value: 3306
          - name: DB_HOST
            value: localhost
      

      Speichern und schließen Sie die Datei.

      Erstellen Sie nun eine Datei namens secrets.yml:

      secrets.yml wird nicht in der Versionskontrolle geprüft. Sie enthält sensible Konfigurationsdaten wie Ihr Datenbankpasswort und den Laravel-App Key. Fügen Sie die folgenden Konfigurationen hinzu und nehmen Sie gegebenenfalls Anpassungen für Ihre Anmeldedaten vor:

      ./helm/secrets.yml

      mysql:
        rootPassword: "your_database_root_password"
        user: your_database_user
        password: "your_database_password"
        database: your_database_name
      
      php:
        envVars:
          - name: APP_KEY
            value: "your_laravel_app_key"
          - name: DB_DATABASE
            value: your_database_name
          - name: DB_USERNAME
            value: your_database_user
          - name: DB_PASSWORD
            value: "your_database_password"
      

      Verwenden Sie für Ihre Produktionsdatenbank starke Benutzername- und Passwortkombinationen und nutzen Sie den gleichen your_laravel_app_key wie oben; öffnen Sie alternativ ein neues Terminalfenster und generieren Sie einen neuen App Key, indem Sie folgenden Befehl ausführen. Dann können Sie den neuen Wert, den Laravel festlegt, in Ihre .env-Datei kopieren:

      • docker run --rm -v $(pwd):/app php:cli php /app/artisan key:generate

      Speichern und schließen Sie secrets.yml.

      Um zu verhindern, dass Ihre secrets.yml-Datei in das Docker-Image integriert oder in der Versionskontrolle gespeichert wird, sollten Sie als Nächstes sowohl der Datei .dockerignore als auch der Datei .gitignore die folgende Zeile hinzufügen. Öffnen und fügen Sie jeder Datei /helm/secrets.yml an oder führen Sie den folgenden Befehl aus, um beide hinzuzufügen:

      • echo '/helm/secrets.yml' >> ./.dockerignore && echo '/helm/secrets.yml' >> ./.gitignore

      Nachdem Sie Helm-Konfigurationsdateien für Ihre Anwendung und das Docker-Image erstellt haben, können Sie dieses Helm Chart nun als neue Version in Ihrem Kubernetes-Cluster installieren. Installieren Sie Ihr Chart im Stammverzeichnis Ihrer Anwendung:

      • helm install laravel-kubernetes -f helm/values.yml -f helm/secrets.yml stable/lamp

      Sie werden eine Ausgabe wie diese sehen:

      Output

      NAME: laravel-kubernetes LAST DEPLOYED: Mon May 18 13:21:20 2020 NAMESPACE: default STATUS: deployed REVISION: 1

      Ihre Anwendung wird eine oder zwei Minuten brauchen, bis sie verfügbar ist. Sie können jedoch folgenden Befehl ausführen, um die Kubernetes-Dienste in Ihrem Cluster zu überwachen:

      Suchen Sie nach dem Namen Ihrer Anwendung:

      Output

      NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) laravel-kubernetes-lamp LoadBalancer your_cluster_ip your_external_ip 80:32175/TCP,3306:32243/TCP

      Wenn Ihr neuer Dienst laravel-kubernetes-lamp unter EXTERNAL-IP eine IP-Adresse anzeigt, können Sie your_external_ip aufrufen, um die in Ihrem Kubernetes-Cluster ausgeführte Anwendung anzuzeigen. Ihre Anwendung wird sich mit Ihrer Datenbank verbinden und Sie werden den Namen der Datenbank unterhalb des Laravel-Logos sehen (genauso wie bei der lokalen Ausführung Ihrer Anwendung in Docker Compose).

      Die Laravel-Anwendung, die in Kubernetes mit dem LAMP Helm Chart ausgeführt wird

      Das Ausführen einer Webanwendung an einer ungesicherten IP-Adresse kann für einen Konzeptnachweis in Ordnung sein, Ihre Website ist jedoch ohne SSL-Zertifikat und einen benutzerdefinierten Domänennamen nicht bereit für die Produktion. Bevor Sie dies im nächsten Schritt einrichten, deinstallieren Sie Ihre Version über die Befehlszeile:

      • helm delete laravel-kubernetes

      Im nächsten Schritt werden Sie auf Grundlage dieser ersten Helm-Konfiguration Ihrer Laravel-Anwendung einen Ingress Controller, ein SSL-Zertifikat und eine benutzerdefinierte Domäne hinzuzufügen.

      Schritt 5 — Hinzufügen von Ingress Controller und SSL zu Ihrem Kubernetes Cluster

      Ein Ingress Controller ist in Kubernetes dafür verantwortlich, die Dienste Ihrer Anwendung im Internet zu verfügbar zu machen. Im vorherigen Schritt hat das LAMP Helm Chart einen DigitalOcean Load Balancer erstellt und Ihre Anwendung direkt über die IP-Adresse des Load Balancer verfügbar gemacht.

      Sie könnten SSL und Ihren Domänennamen direkt im Load Balancer terminieren; da Sie jedoch in Kubernetes arbeiten, kann es praktischer sein, alles an einem Ort zu verwalten. Deutlich ausführlichere Informationen zu Ingress Controllern und Details zu den folgenden Schritten finden Sie unter Verwenden eines Nginx Ingress in DigitalOcean Kubernetes mit Helm.

      Das LAMP Helm Chart enthält eine Konfigurationsoption zur Unterstützung von Ingress. Öffnen Sie die Datei helm/values.yml:

      Fügen Sie jetzt die folgenden Zeilen hinzu:

      ./helm/values.yml

      ...
      # Use Ingress Controller
      service:
        type: ClusterIP
        HTTPPort: 80
      ingress:
        enabled: true
        domain: your_domain
      

      Dadurch wird Ihre Bereitstellung angewiesen, keinen Load Balancer zu installieren und die Anwendung stattdessen an Port 80 des Kubernetes-Clusters verfügbar zu machen, wo der Ingress Controller sie im Internet verfügbar macht. Speichern und schließen Sie values.yml.

      Führen Sie nun den Befehl helm install aus, den Sie zuvor ausgeführt haben, damit Ihre Laravel-Anwendung wieder ausgeführt wird. Stellen Sie sicher, dass Sie den Befehl aus dem Stammverzeichnis Ihrer Anwendung ausführen:

      • helm install laravel-kubernetes -f helm/values.yml -f helm/secrets.yml stable/lamp

      Installieren Sie als Nächstes den Controller nginx-ingress in Ihrem Kubernetes-Cluster mit dem von Kubernetes verwalteten Nginx Ingress Controller:

      • helm install nginx-ingress stable/nginx-ingress --set controller.publishService.enabled=true

      Nach der Installation werden Sie eine Ausgabe wie diese sehen:

      Output

      NAME: nginx-ingress LAST DEPLOYED: Mon May 18 13:28:34 2020 NAMESPACE: default STATUS: deployed REVISION: 1

      Außerdem benötigen Sie eine Ingress-Ressource, um die Bereitstellung Ihrer Laravel-Anwendung verfügbar zu machen. Erstellen Sie im Stammverzeichnis Ihrer Anwendung eine neue Datei namens ingress.yml:

      Diese Datei legt den Host, den SSL-Zertifikatmanager und den Backend-Dienst und Port-Namen der Anwendung fest. Fügen Sie die folgenden Konfigurationen hinzu, wobei Sie your_domain durch die Domäne Ihrer Wahl ersetzen:

      ./ingress.yml

      apiVersion: networking.k8s.io/v1beta1
      kind: Ingress
      metadata:
        name: laravel-kubernetes-ingress
        annotations:
          kubernetes.io/ingress.class: nginx
          cert-manager.io/cluster-issuer: letsencrypt-prod
      spec:
        tls:
          - hosts:
              - your_domain
            secretName: laravel-kubernetes-tls
        rules:
          - host: your_domain
            http:
              paths:
                - backend:
                    serviceName: laravel-kubernetes-lamp
                    servicePort: 80
      

      Speichern und schließen Sie die Datei.

      Als Nächstes sollten Sie Cert-Manager installieren und einen Aussteller erstellen, mit dem Sie mit Let’s Encrypt SSL-Zertifikate für die Produktion erzeugen können. Cert-Manager erfordert benutzerdefinierte Ressourcendefinitionen, die Sie aus dem Cert-Manager-Repository über die Befehlszeile anwenden können:

      • kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v0.15.0/cert-manager.crds.yaml

      Dadurch wird eine Reihe von Kubernetes-Ressourcen erstellt, die in der Befehlszeile angezeigt werden:

      Output

      customresourcedefinition.apiextensions.k8s.io/certificaterequests.cert-manager.io created customresourcedefinition.apiextensions.k8s.io/certificates.cert-manager.io created customresourcedefinition.apiextensions.k8s.io/challenges.acme.cert-manager.io created customresourcedefinition.apiextensions.k8s.io/clusterissuers.cert-manager.io created customresourcedefinition.apiextensions.k8s.io/issuers.cert-manager.io created customresourcedefinition.apiextensions.k8s.io/orders.acme.cert-manager.io create

      Außerdem benötigt Cert-Manager einen Namespace zur Isolation in Ihrem Kubernetes-Cluster:

      • kubectl create namespace cert-manager

      Sie sehen diese Ausgabe:

      Output

      namespace/cert-manager created

      Da Cert-Manager von Jetstack keines der von Kubernetes verwalteten Charts ist, müssen Sie auch das Jetstack Helm-Repository hinzufügen. Führen Sie folgenden Befehl aus, um es in Helm verfügbar zu machen:

      • helm repo add jetstack https://charts.jetstack.io

      Bei erfolgreicher Ergänzung erhalten Sie folgende Ausgabe:

      Output

      "jetstack" has been added to your repositories

      Jetzt können Sie Cert-Manager in Ihrem Kubernetes-Cluster im Namespace cert-manager installieren:

      • helm install cert-manager --version v0.15.0 --namespace cert-manager jetstack/cert-manager

      Nach Abschluss sehen Sie eine Zusammenfassung der Bereitstellung, die in etwa wie folgt aussieht:

      Output

      NAME: cert-manager LAST DEPLOYED: Mon May 18 13:32:08 2020 NAMESPACE: cert-manager STATUS: deployed REVISION: 1

      Die letzte Datei, die Sie dem Stammverzeichnis Ihrer Laravel-Anwendung hinzufügen müssen, ist eine Kubernetes-Konfigurationsdatei namens production_issuer.yml. Erstellen Sie die Datei:

      • nano ./production_issuer.yml

      Fügen Sie nun Folgendes hinzu:

      apiVersion: cert-manager.io/v1alpha2
      kind: ClusterIssuer
      metadata:
        name: letsencrypt-prod
      spec:
        acme:
          # Email address used for ACME registration
          email: your_email_address
          server: https://acme-v02.api.letsencrypt.org/directory
          privateKeySecretRef:
            # Name of a secret used to store the ACME account private key
            name: letsencrypt-prod-private-key
          # Add a single challenge solver, HTTP01 using nginx
          solvers:
            - http01:
                ingress:
                  class: nginx
      

      Speichern und schließen Sie die Datei.

      Let’s Encrypt sendet an your_email_address sämtliche wichtigen Hinweise und Warnungen zum Ablauf von Zertifikaten; darum sollten Sie eine Adresse hinzufügen, die Sie regelmäßig prüfen. Speichern Sie diese Datei und erstellen Sie eine neue Ressource sowohl für Ihre Ingress-Ressource als auch den Produktionsaussteller in Ihrem Kubernetes-Cluster:

      • kubectl create -f ingress.yml
      • kubectl create -f production_issuer.yml

      Aktualisieren Sie schließlich die DNS-Einträge Ihres Domänennamens so, dass ein A-Eintrag auf die IP-Adresse Ihres Load Balancer verweist. Um nach der IP-Adresse Ihres Ingress Controller zu suchen, geben Sie Folgendes ein:

      • kubectl get service nginx-ingress-controller

      Output

      NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx-ingress-controller LoadBalancer your_cluster_ip your_external_ip 80:30187/TCP,443:31468/TCP 6m10s

      Verwenden Sie die Adresse your_external_ip als IP-Adresse für Ihren DNS A-Eintrag. Das Verfahren zur Aktualisierung Ihrer DNS-Einträge variiert, je nachdem wo Sie Ihre Domänennamen und das DNS-Hosting verwalten. Wenn Sie DigitalOcean verwenden, können Sie jedoch unseren Leitfaden zum Verwalten von DNS-Einträgen konsultieren.

      Sobald Ihre DNS-Einträge aktualisiert und das SSL-Zertifikat generiert wurden, wird Ihre Anwendung in your_domain verfügbar und SSL aktiviert.

      Die Laravel-Anwendung mit SSL-Terminierung und einem benutzerdefinierten Domänennamen

      Zwar sind Ihre PHP-Anwendung und Ihre Datenbank bereits miteinander verbunden, doch müssen Sie noch Datenbankmigrationen ausführen. Im letzten Schritt erfahren Sie, wie Sie Artisan-Befehle in Ihrem Kubernetes-Pod ausführen können, um Datenbankmigrationen und andere häufige Wartungsaufgaben durchzuführen.

      Schritt 6 — Ausführen von Remotebefehlen

      Zwar wird Ihre Laravel-Anwendung ausgeführt und ist mit der MySQL-Datenbank in Kubernetes verbunden, doch gibt es mehrere gängige Aufgaben, die Sie in einer neuen Laravel-Installation erledigen sollten. Eine gängige Aufgabe, die Sie ausführen sollten, sind Datenbankmigrationen.

      Bevor Sie in Ihrer Laravel-Anwendung einen Artisan-Befehl ausführen können, müssen Sie den Namen des Pods kennen, das Ihren Laravel-Anwendungscontainer ausführt. Mit der Befehlszeile können Sie alle Pods in Ihrem Kubernetes-Cluster anzeigen:

      Sie werden eine Ausgabe wie diese sehen:

      Output

      NAME READY STATUS RESTARTS AGE laravel-kubernetes-lamp-77fb989b46-wczgb 2/2 Running 0 16m

      Wählen Sie das Pod für Ihre laravel-kubernetes-lamp-...-Bereitstellung aus. Stellen Sie sicher, dass Sie den Namen in Ihrer Ausgabe verwenden und nicht den oben aufgeführten Namen. Jetzt können Sie kubectl exec dafür ausführen. Beispielsweise führen Sie eine Datenbankmigration mit dem Befehl artisan migrate aus. Sie fügen das Flag --force hinzu, da Sie das Pod in der Produktion ausführen:

      • kubectl exec laravel-kubernetes-lamp-77fb989b46-wczgb -- php artisan migrate --force

      Dieser Befehl wird eine Ausgabe erzeugen:

      Output

      Migration table created successfully. Migrating: 2014_10_12_000000_create_users_table Migrated: 2014_10_12_000000_create_users_table (0.16 seconds) Migrating: 2019_08_19_000000_create_failed_jobs_table Migrated: 2019_08_19_000000_create_failed_jobs_table (0.05 seconds)

      Sie haben Laravel 7 und MySQL nun erfolgreich in Kubernetes bereitgestellt und eine wichtige Aufgabe zur Datenbankwartung durchgeführt.

      Zusammenfassung

      In diesem Tutorial haben Sie gelernt, wie Sie eine Laravel PHP-Anwendung containerisieren, mit einer MySQL-Datenbank verbinden, ein Docker-Image mit Ihrem Code an Docker Hub pushen und dann ein Helm Chart nutzen, um das Image in einem DigitalOcean Kubernetes-Cluster bereitzustellen. Schließlich haben Sie SSL und einen benutzerdefinierten Domänennamen hinzugefügt und erfahren, wie Sie in Ihren laufenden Pods Befehlszeilentools ausführen.

      Kubernetes and Helm bieten Ihnen eine Reihe von Vorteilen gegenüber dem herkömmlichen LAMP-Stack-Hosting: Skalierbarkeit, die Fähigkeit, Dienste ohne direkte Anmeldung bei Ihrem Server auszutauschen, Tools zur Durchführung von rollierenden Upgrades und Kontrolle über Ihre Hostingumgebung. Es muss jedoch gesagt werden, dass die Komplexität der anfänglichen Containerisierung und Konfiguration Ihrer Anwendung am Anfang eine relativ hohe Barriere darstellt. Mit diesem Leitfaden als Ausgangspunkt wird die Bereitstellung von Laravel in Kubernetes jedoch verständlicher. Vielleicht wollen Sie nun mehr über die Vorzüge von Laravel oder das Hinzufügen von Überwachungstools (wie Linkerd) zu Kubernetes zu erfahren (manuell installierbar mit unserem Leitfaden oder mit einem DigitalOcean 1-Click).



      Source link

      So verwenden Sie Go mit MongoDB mithilfe des MongoDB Go-Treibers


      Der Autor wählte die Free Software Foundation, um eine Spende im Rahmen des Programms Write for DOnations zu erhalten.

      Einführung

      Nachdem MongoDB sich viele Jahre lang auf von der Community entwickelte Lösungen verlassen hatte, gab MongoDB bekannt, dass sie an einem offiziellen Treiber für Go arbeiten. Im März 2019 erreichte dieser neue Treiber mit der Veröffentlichung von v1.0.0 den Status „Produktionsbereit“ und wurde seitdem kontinuierlich aktualisiert.

      Wie die anderen offiziellen MongoDB-Treiber ist der Go-Treiber für die Go-Programmiersprache typisch und bietet eine einfache Möglichkeit, MongoDB als Datenbanklösung für ein Go-Programm zu verwenden. Er ist vollständig in die MongoDB-API integriert und stellt alle Abfrage-, Indexierungs- und Aggregationsfunktionen der API sowie andere erweiterte Funktionen zur Verfügung. Im Gegensatz zu Bibliotheken von Drittanbietern wird er von MongoDB-Ingenieuren vollständig unterstützt, sodass Sie sicher sein können, dass er weiterentwickelt und gewartet wird.

      In diesem Tutorial lernen Sie den offiziellen MongoDB Go-Treiber kennen. Sie installieren den Treiber, stellen eine Verbindung zu einer MongoDB-Datenbank her und führen mehrere CRUD-Vorgänge aus. Dabei erstellen Sie ein Task-Manager-Programm zum Verwalten von Aufgaben über die Befehlszeile.

      Voraussetzungen

      Für dieses Tutorial benötigen Sie Folgendes:

      • Go auf Ihrem Computer installiert und einen Go-Arbeitsbereich, der wie folgt konfiguriert wird: Installieren von Go und Einrichten einer lokalen Programmierumgebung. In diesem Tutorial wird das Projekt als tasker bezeichnet. Sie müssen Go v1.11 oder höher auf Ihrem Computer mit aktivierten Go-Modulen installiert haben.
      • MongoDB für Ihr Betriebssystem gemäß der Installation von MongoDB installiert. MongoDB 2.6 oder höher ist die Mindestversion, die vom MongoDB Go-Treiber unterstützt wird.

      Wenn Sie Go v1.11 oder 1.12 verwenden, stellen Sie sicher, dass Go Modules aktiviert ist, indem Sie die Umgebungsvariable GO111MODULE wie folgt auf on setzen:

      Weitere Informationen zum Implementieren von Umgebungsvariablen finden Sie in diesem Tutorial zum Lesen und Festlegen von Umgebungs- und Shell-Variablen.

      Die in diesem Leitfaden gezeigten Befehle und Codes wurden mit Go v1.14.1 und MongoDB v3.6.3 getestet.

      Schritt 1 — Installieren des MongoDB Go-Treibers

      In diesem Schritt installieren Sie das Paket Go Driver für MongoDB und importieren es in Ihr Projekt. Außerdem stellen Sie eine Verbindung zu Ihrer MongoDB-Datenbank her und überprüfen den Status der Verbindung.

      Fahren Sie fort und erstellen Sie ein neues Verzeichnis für dieses Tutorial in Ihrem Dateisystem:

      Sobald Ihr Projektverzeichnis eingerichtet ist, ändern Sie es mit dem folgenden Befehl:

      Initialisieren Sie als Nächstes das Go-Projekt mit einer go.mod-Datei. Diese Datei definiert die Projektanforderungen und die Abhängigkeiten ihrer richtigen Versionen:

      Wenn Ihr Projektverzeichnis außerhalb von $GOPATH ist, müssen Sie den Importpfad für Ihr Modul wie folgt angeben:

      • go mod init github.com/<your_username>/tasker

      Nun sieht Ihre Datei go.mod wie folgt aus:

      go.mod

      module github.com/<your_username>/tasker
      
      go 1.14
      

      Fügen Sie mit dem folgenden Befehl den MongoDB Go-Treiber als eine Abhängigkeit für Ihr Projekt hinzu:

      • go get go.mongodb.org/mongo-driver

      Sie sehen eine Ausgabe wie die folgende:

      Output

      go: downloading go.mongodb.org/mongo-driver v1.3.2 go: go.mongodb.org/mongo-driver upgrade => v1.3.2

      Nun sieht Ihre Datei go.mod wie folgt aus:

      go.mod

      module github.com/<your_username>/tasker
      
      go 1.14
      
      require go.mongodb.org/mongo-driver v1.3.1 // indirect
      

      Erstellen Sie als Nächstes eine main.go-Datei in Ihrem Projektstamm und öffnen Sie sie in Ihrem Texteditor:

      Importieren Sie die folgenden Pakete in Ihre main.go-Datei, um mit dem Treiber zu beginnen:

      main.go

      package main
      
      import (
          "context"
          "log"
      
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      

      Hier fügen Sie die Pakete mongo und options hinzu, die der MongoDB Go-Treiber bereitstellt.

      Erstellen Sie nach Ihren Importen einen neuen MongoDB-Client und stellen Sie eine Verbindung zu Ihrem laufenden MongoDB-Server her:

      main.go

      . . .
      var collection *mongo.Collection
      var ctx = context.TODO()
      
      func init() {
          clientOptions := options.Client().ApplyURI("mongodb://localhost:27017/")
          client, err := mongo.Connect(ctx, clientOptions)
          if err != nil {
              log.Fatal(err)
          }
      }
      

      mongo.Connect() akzeptiert ein Objekt Context und ein options.ClientOptions-Objekt, mit denen die Verbindungszeichenfolge und andere Treibereinstellungen festgelegt werden. In der Dokumentation zum Optionspaket finden Sie Informationen zu den verfügbaren Konfigurationsoptionen.

      Context ist wie eine Zeitüberschreitung oder eine Frist, die angibt, wann ein Vorgang nicht mehr ausgeführt und zurückgegeben werden soll. Dies hilft, Leistungseinbußen auf Produktionssystemen zu vermeiden, wenn bestimmte Vorgänge langsam ausgeführt werden. In diesem Code übergeben Sie context.TODO(), um anzuzeigen, dass Sie nicht sicher sind, welchen Kontext Sie derzeit verwenden sollen, aber Sie planen, in Zukunft einen hinzuzufügen.

      Stellen Sie als Nächstes sicher, dass Ihr MongoDB-Server mithilfe der Ping-Methode gefunden und erfolgreich verbunden wurde. Fügen Sie den folgenden Code in die init-Funktion ein:

      main.go

      . . .
          log.Fatal(err)
        }
      
        err = client.Ping(ctx, nil)
        if err != nil {
          log.Fatal(err)
        }
      }
      

      Wenn beim Herstellen einer Verbindung zur Datenbank Fehler auftreten, sollte das Programm abstürzen, während Sie versuchen, das Problem zu beheben, da es keinen Sinn macht, das Programm ohne aktive Datenbankverbindung auszuführen.

      Fügen Sie den folgenden Code hinzu, um eine Datenbank zu erstellen:

      main.go

      . . .
        err = client.Ping(ctx, nil)
        if err != nil {
          log.Fatal(err)
        }
      
        collection = client.Database("tasker").Collection("tasks")
      }
      

      Sie erstellen eine tasker-Datenbank und eine task-Sammlung, um die zu erstellenden Aufgaben zu speichern. Sie richten collection auch als Variable auf Paketebene ein, damit Sie die Datenbankverbindung im gesamten Paket wiederverwenden können.

      Speichern und schließen Sie die Datei.

      Das vollständige main.go ist nun wie folgt:

      main.go

      package main
      
      import (
          "context"
          "log"
      
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      
      var collection *mongo.Collection
      var ctx = context.TODO()
      
      func init() {
          clientOptions := options.Client().ApplyURI("mongodb://localhost:27017/")
          client, err := mongo.Connect(ctx, clientOptions)
          if err != nil {
              log.Fatal(err)
          }
      
          err = client.Ping(ctx, nil)
          if err != nil {
              log.Fatal(err)
          }
      
          collection = client.Database("tasker").Collection("tasks")
      }
      

      Sie haben Ihr Programm eingerichtet, um über den Go-Treiber eine Verbindung zu Ihrem MongoDB-Server zu erhalten. Im nächsten Schritt erstellen Sie Ihr Task-Manager-Programm.

      Schritt 2 — Erstellen eines CLI-Programms

      In diesem Schritt installieren Sie das bekannte cli-Paket, um die Entwicklung Ihres Task-Manager-Programms zu unterstützen. Es bietet eine Schnittstelle, über die Sie schnell moderne Befehlszeilentools erstellen können. Dieses Paket bietet beispielsweise die Möglichkeit, Unterbefehle für Ihr Programm zu definieren, um eine git-ähnliche Befehlszeilenerfahrung zu erzielen.

      Führen Sie den folgenden Befehl aus, um das Paket als Abhängigkeit hinzuzufügen:

      • go get github.com/urfave/cli/v2

      Öffnen Sie als Nächstes Ihre main.go-Datei erneut:

      Fügen Sie Ihrer main.go-Datei den folgenden hervorgehobenen Code hinzu:

      main.go

      package main
      
      import (
          "context"
          "log"
          "os"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      . . .
      

      Sie importieren das cli-Paket wie erwähnt. Außerdem importieren Sie das Paket os, das Sie verwenden, um Befehlszeilenargumente an Ihr Programm zu übergeben:

      Fügen Sie nach Ihrer init-Funktion den folgenden Code hinzu, um Ihr CLI-Programm zu erstellen und den Code zu kompilieren:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:     "tasker",
              Usage:    "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{},
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      

      Dieses Snippet erstellt ein CLI-Programm namens tasker und fügt eine kurze Verwendungsbeschreibung hinzu, die beim Ausführen des Programms ausgedruckt wird. Im Befehlsfenster fügen Sie Befehle für Ihr Programm hinzu. Der Befehl Run analysiert die Argumente auf den entsprechenden Befehl.

      Speichern und schließen Sie Ihre Datei.

      Hier ist der Befehl, den Sie zum Erstellen und Ausführen des Programms benötigen:

      Sie sehen die folgende Ausgabe:

      Output

      NAME: tasker - A simple CLI program to manage your tasks USAGE: main [global options] command [command options] [arguments...] COMMANDS: help, h Shows a list of commands or help for one command GLOBAL OPTIONS: --help, -h show help (default: false)

      Das Programm wird ausgeführt und zeigt einen Hilfetext an, in dem Sie erfahren, was das Programm kann und wie es verwendet wird.

      In den nächsten Schritten verbessern Sie die Nützlichkeit Ihres Programms, indem Sie Unterbefehle hinzufügen, um Ihre Aufgaben in MongoDB zu verwalten.

      Schritt 3 — Erstellen einer Aufgabe

      In diesem Schritt fügen Sie Ihrem CLI-Programm mithilfe des cli-Pakets einen Unterbefehl hinzu. Am Ende dieses Abschnitts können Sie Ihrer MongoDB-Datenbank eine neue Aufgabe hinzufügen, indem Sie einen neuen add-Befehl in Ihrem CLI-Programm verwenden.

      Öffnen Sie zunächst Ihre main.go-Datei:

      Importieren Sie als Nächstes die Pakete go.mongodb.org/mongo-driver/bson/primitive, time und errors:

      main.go

      package main
      
      import (
          "context"
          "errors"
          "log"
          "os"
          "time"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/bson/primitive"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      . . .
      

      Erstellen Sie dann eine neue Struktur, um eine einzelne Aufgabe in der Datenbank darzustellen, und fügen Sie sie unmittelbar vor der Hauptfunktion ein:

      main.go

      . . .
      type Task struct {
          ID        primitive.ObjectID `bson:"_id"`
          CreatedAt time.Time          `bson:"created_at"`
          UpdatedAt time.Time          `bson:"updated_at"`
          Text      string             `bson:"text"`
          Completed bool               `bson:"completed"`
      }
      . . .
      

      Sie verwenden das primitive Paket, um den Typ der ID jeder Aufgabe festzulegen, da MongoDB standardmäßig ObjectIDs für das Feld _id verwendet. Ein weiteres Standardverhalten von MongoDB besteht darin, dass der Feldname in Kleinbuchstaben als Schlüssel für jedes exportierte Feld verwendet wird, wenn es serialisiert wird. Dies kann jedoch mithilfe von bson struct-Tags geändert werden.

      Erstellen Sie als Nächstes eine Funktion, die eine Instanz der Aufgabe empfängt und in der Datenbank speichert. Fügen Sie dieses Snippet nach der Hauptfunktion hinzu:

      main.go

      . . .
      func createTask(task *Task) error {
          _, err := collection.InsertOne(ctx, task)
        return err
      }
      . . .
      

      Die Methode collection.InsertOne() fügt die bereitgestellte Aufgabe in die Datenbanksammlung ein und gibt die ID des eingefügten Dokuments zurück. Da Sie diese ID nicht benötigen, verwerfen Sie sie, indem Sie sie dem Unterstrichoperator zuweisen.

      Der nächste Schritt besteht darin, Ihrem Task-Manager-Programm einen neuen Befehl zum Erstellen neuer Aufgaben hinzuzufügen. Nennen wir ihn add:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
              },
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      

      Jeder neue Befehl, der Ihrem CLI-Programm hinzugefügt wird, wird im Fenster Befehle platziert. Jeder besteht aus einem Namen, einer Verwendungsbeschreibung und einer Aktion. Dies ist der Code, der bei der Befehlsausführung ausgeführt wird.

      In diesem Code sammeln Sie das erste add-Argument und verwenden es, um die Texteigenschaft einer neuen Aufgabeninstanz festzulegen, während Sie die entsprechenden Standardeinstellungen für die anderen Eigenschaften zuweisen. Die neue Aufgabe wird anschließend an createTask weitergeleitet, die die Aufgabe in die Datenbank einfügt und nil zurückgibt, wenn alles gut geht und der Befehl beendet wird.

      Speichern und schließen Sie Ihre Datei.

      Testen Sie es, indem Sie mit dem Befehl add einige Aufgaben hinzufügen. Bei Erfolg werden keine Fehler auf Ihrem Bildschirm angezeigt:

      • go run main.go add "Learn Go"
      • go run main.go add "Read a book"

      Nachdem Sie nun erfolgreich Aufgaben hinzufügen können, implementieren wir eine Möglichkeit, alle Aufgaben anzuzeigen, die Sie der Datenbank hinzugefügt haben.

      Schritt 4 — Auflisten aller Aufgaben

      Das Auflisten der Dokumente in einer Sammlung kann mit der Methode collection.Find() erfolgen, die einen Filter sowie einen Zeiger auf einen Wert erwartet, in den das Ergebnis dekodiert werden kann. Der Rückgabewert ist ein Cursor, der eine Reihe an Dokumenten bereitstellt, die einzeln durchlaufen und dekodiert werden können. Der Cursor wird dann geschlossen, sobald er erschöpft ist.

      Öffnen Sie Ihre main.go-Datei:

      Stellen Sie sicher, dass das Paket bson importiert wird:

      main.go

      package main
      
      import (
          "context"
          "errors"
          "log"
          "os"
          "time"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/bson"
          "go.mongodb.org/mongo-driver/bson/primitive"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      . . .
      

      Erstellen Sie dann unmittelbar nach createTask die folgenden Funktionen:

      main.go

      . . .
      func getAll() ([]*Task, error) {
        // passing bson.D{{}} matches all documents in the collection
          filter := bson.D{{}}
          return filterTasks(filter)
      }
      
      func filterTasks(filter interface{}) ([]*Task, error) {
          // A slice of tasks for storing the decoded documents
          var tasks []*Task
      
          cur, err := collection.Find(ctx, filter)
          if err != nil {
              return tasks, err
          }
      
          for cur.Next(ctx) {
              var t Task
              err := cur.Decode(&t)
              if err != nil {
                  return tasks, err
              }
      
              tasks = append(tasks, &t)
          }
      
          if err := cur.Err(); err != nil {
              return tasks, err
          }
      
        // once exhausted, close the cursor
          cur.Close(ctx)
      
          if len(tasks) == 0 {
              return tasks, mongo.ErrNoDocuments
          }
      
          return tasks, nil
      }
      

      Mit BSON (Binary-coded JSON) werden Dokumente in einer MongoDB-Datenbank dargestellt, und das bson-Paket hilft uns bei der Arbeit mit BSON-Objekten in Go. Der in der Funktion getAll() verwendete Typ bson.D stellt ein BSON-Dokument dar und wird dort verwendet, wo die Reihenfolge der Eigenschaften von Bedeutung ist. Indem Sie bson.D{{}} als Filter an filterTasks() übergeben, geben Sie an, dass Sie mit allen Dokumenten in der Sammlung übereinstimmen möchten.

      In der Funktion filterTasks() iterieren Sie über den von der collection.Find()-Methode zurückgegebenen Cursor und dekodieren jedes Dokument in eine Instanz der Aufgabe. Jede Aufgabe wird dann an den zu Beginn der Funktion erstellten Aufgabenbereich angehängt. Sobald der Cursor erschöpft ist, wird er geschlossen und das Aufgabenfenster zurückgegeben.

      Bevor Sie einen Befehl zum Auflisten aller Aufgaben erstellen, erstellen wir eine Hilfsfunktion, die einen Teil der Aufgaben übernimmt und in die Standardausgabe druckt. Sie verwenden das Paket color, um die Ausgabe zu färben.

      Bevor Sie dieses Paket verwenden können, installieren Sie es mit:

      • go get gopkg.in/gookit/color.v1

      Sie sehen die folgende Ausgabe:

      Output

      go: downloading gopkg.in/gookit/color.v1 v1.1.6 go: gopkg.in/gookit/color.v1 upgrade => v1.1.6

      Und importieren Sie sie zusammen mit dem fmt-Paket in Ihre main.go-Datei:

      main.go

      package main
      
      import (
          "context"
          "errors"
        "fmt"
          "log"
          "os"
          "time"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/bson"
          "go.mongodb.org/mongo-driver/bson/primitive"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
          "gopkg.in/gookit/color.v1"
      )
      . . .
      

      Erstellen Sie als Nächstes eine neue printTasks-Funktion, die Ihrer Hauptfunktion folgt:

      main.go

      . . .
      func printTasks(tasks []*Task) {
          for i, v := range tasks {
              if v.Completed {
                  color.Green.Printf("%d: %sn", i+1, v.Text)
              } else {
                  color.Yellow.Printf("%d: %sn", i+1, v.Text)
              }
          }
      }
      . . .
      

      Diese printTasks-Funktion übernimmt eine Reihe von Aufgaben, durchläuft jede einzelne und druckt sie in der Standardausgabe aus. Dabei wird die grüne Farbe verwendet, um abgeschlossene Aufgaben anzuzeigen, und gelb für unvollständige Aufgaben.

      Fahren Sie fort und fügen Sie die folgenden hervorgehobenen Zeilen hinzu, um einen neuen all-Befehl im Fenster Befehle zu erstellen. Dieser Befehl druckt alle hinzugefügten Aufgaben in die Standardausgabe:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
              },
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      
      . . .
      

      Der Befehl all ruft alle in der Datenbank vorhandenen Aufgaben ab und druckt sie in die Standardausgabe. Wenn keine Aufgaben vorhanden sind, wird stattdessen eine Eingabeaufforderung zum Hinzufügen einer neuen Aufgabe gedruckt.

      Speichern und schließen Sie Ihre Datei.

      Erstellen und führen Sie Ihr Programm mit dem Befehl all aus:

      Er wird alle Aufgaben, die Sie bisher hinzugefügt haben, auflisten:

      Output

      1: Learn Go 2: Read a book

      Nachdem Sie nun alle Aufgaben in der Datenbank anzeigen können, können Sie im nächsten Schritt die Möglichkeit hinzufügen, eine Aufgabe als erledigt zu markieren.

      Schritt 5 — Abschließen einer Aufgabe

      In diesem Schritt erstellen Sie einen neuen Unterbefehl namens done, mit dem Sie eine vorhandene Aufgabe in der Datenbank als erledigt markieren können. Um eine Aufgabe als abgeschlossen zu markieren, können Sie die Methode collection.FindOneAndUpdate() verwenden. Sie ermöglicht es Ihnen, ein Dokument in einer Sammlung zu lokalisieren und einige oder alle seine Eigenschaften zu aktualisieren. Diese Methode erfordert einen Filter zum Auffinden des Dokuments und ein Aktualisierungsdokument zum Beschreiben des Vorgangs. Beide werden mit den Typen bson.D erstellt.

      Beginnen Sie durch Öffnen Ihrer main.go-Datei:

      Fügen Sie den folgenden Snippet nach Ihrer Funktion filterTasks ein:

      main.go

      . . .
      func completeTask(text string) error {
          filter := bson.D{primitive.E{Key: "text", Value: text}}
      
          update := bson.D{primitive.E{Key: "$set", Value: bson.D{
              primitive.E{Key: "completed", Value: true},
          }}}
      
          t := &Task{}
          return collection.FindOneAndUpdate(ctx, filter, update).Decode(t)
      }
      . . .
      

      Die Funktion entspricht dem ersten Dokument, in dem die Texteigenschaft dem Textparameter entspricht. Das Dokument update gibt an, dass die Eigenschaft completed auf true gesetzt wird. Wenn beim Vorgang FindOneAndUpdate() ein Fehler auftritt, wird dieser von completeTask() zurückgegeben. Andernfalls wird nil zurückgegeben.

      Als Nächstes fügen wir Ihrem CLI-Programm einen neuen done-Befehl hinzu, der eine Aufgabe als erledigt markiert:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:    "done",
                      Aliases: []string{"d"},
                      Usage:   "complete a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          return completeTask(text)
                      },
                  },
              },
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      
      . . .
      

      Sie verwenden das an den Befehl done übergebene Argument, um das erste Dokument zu finden, dessen Texteigenschaft übereinstimmt. Wenn es gefunden wurde, wird die Eigenschaft completed im Dokument auf true gesetzt.

      Speichern und schließen Sie Ihre Datei.

      Führen Sie dann Ihr Programm mit dem Befehl done aus:

      • go run main.go done "Learn Go"

      Wenn Sie den Befehl all erneut verwenden, werden Sie feststellen, dass die als erledigt markierte Aufgabe jetzt grün gedruckt wird.

      Screenshot der Terminalausgabe nach dem Ausführen einer Aufgabe

      Manchmal möchten Sie nur Aufgaben anzeigen, die noch nicht erledigt sind. Wir fügen diese Eigenschaft als Nächstes hinzu.

      Schritt 6 — Nur ausstehende Aufgaben anzeigen

      In diesem Schritt integrieren Sie einen Code zum Abrufen ausstehender Aufgaben aus der Datenbank mithilfe des MongoDB-Treibers. Ausstehende Aufgaben sind Aufgaben, deren Eigenschaft completed auf true gesetzt ist.

      Lassen Sie uns eine neue Funktion hinzufügen, die Aufgaben abruft, die noch nicht abgeschlossen sind. Öffnen Sie Ihre main.go-Datei:

      Fügen Sie dann dieses Snippet nach der Funktion completeTask hinzu:

      main.go

      . . .
      func getPending() ([]*Task, error) {
          filter := bson.D{
              primitive.E{Key: "completed", Value: false},
          }
      
          return filterTasks(filter)
      }
      . . .
      

      Sie erstellen einen Filter mit den Paketen bson und primitive aus dem MongoDB-Treiber, der mit Dokumenten übereinstimmt, deren Eigenschaft completed auf true gesetzt ist. Das Segment ausstehender Aufgaben wird dann an den Anrufer zurückgegeben.

      Anstatt einen neuen Befehl zum Auflisten ausstehender Aufgaben zu erstellen, sollten Sie ihn zur Standardaktion machen, wenn Sie das Programm ohne Befehle ausführen. Sie können dies tun, indem Sie dem Programm eine Action-Eigenschaft wie folgt hinzufügen:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Action: func(c *cli.Context) error {
                  tasks, err := getPending()
                  if err != nil {
                      if err == mongo.ErrNoDocuments {
                          fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                          return nil
                      }
      
                      return err
                  }
      
                  printTasks(tasks)
                  return nil
              },
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
      . . .
      

      Die Action-Eigenschaft führt eine Standardaktion aus, wenn das Programm ohne Unterbefehle ausgeführt wird. Hier wird eine Logik für das Auflisten ausstehender Aufgaben platziert. Die Funktion getPending() wird aufgerufen und die resultierenden Aufgaben werden mit printTasks() in die Standardausgabe gedruckt. Wenn keine ausstehenden Aufgaben vorhanden sind, wird stattdessen eine Eingabeaufforderung angezeigt, in der der Benutzer aufgefordert wird, mit dem Befehl add eine neue Aufgabe hinzuzufügen.

      Speichern und schließen Sie Ihre Datei.

      Wenn Sie das Programm jetzt ausführen, ohne Befehle hinzuzufügen, werden alle ausstehenden Aufgaben in der Datenbank aufgelistet:

      Sie sehen die folgende Ausgabe:

      Output

      1: Read a book

      Nachdem Sie unvollständige Aufgaben aufgelistet haben, fügen wir einen weiteren Befehl hinzu, mit dem Sie nur abgeschlossene Aufgaben anzeigen können.

      Schritt 7 — Anzeigen von abgeschlossenen Aufgaben

      In diesem Schritt fügen Sie einen neuen Unterbefehl finished hinzu, der erledigte Aufgaben aus der Datenbank abruft und auf dem Bildschirm anzeigt. Dies beinhaltet das Filtern und Zurückgeben von Aufgaben, deren Eigenschaft completed auf true gesetzt ist.

      Öffnen Sie Ihre main.go-Datei:

      Fügen Sie dann am Ende Ihrer Datei den folgenden Code hinzu:

      main.go

      . . .
      func getFinished() ([]*Task, error) {
          filter := bson.D{
              primitive.E{Key: "completed", Value: true},
          }
      
          return filterTasks(filter)
      }
      . . .
      

      Ähnlich wie bei der Funktion getPending() haben Sie eine Funktion getFinished() hinzugefügt, die einen Teil der abgeschlossenen Aufgaben zurückgibt. In diesem Fall hat der Filter die Eigenschaft completed auf true gesetzt, sodass nur die Dokumente zurückgegeben werden, die dieser Bedingung entsprechen.

      Erstellen Sie als Nächstes einen Befehl finished, der alle erledigten Aufgaben druckt:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Action: func(c *cli.Context) error {
                  tasks, err := getPending()
                  if err != nil {
                      if err == mongo.ErrNoDocuments {
                          fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                          return nil
                      }
      
                      return err
                  }
      
                  printTasks(tasks)
                  return nil
              },
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:    "done",
                      Aliases: []string{"d"},
                      Usage:   "complete a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          return completeTask(text)
                      },
                  },
                  {
                      Name:    "finished",
                      Aliases: []string{"f"},
                      Usage:   "list completed tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getFinished()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `done 'task'` to complete a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
              }
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      . . .
      

      Der Befehl finished ruft Aufgaben ab, deren Eigenschaft completed über die hier erstellte Funktion getFinished() auf true gesetzt ist. Anschließend werden sie an die Funktion printTasks übergeben, sodass sie in der Standardausgabe gedruckt werden.

      Speichern und schließen Sie Ihre Datei.

      Führen Sie den folgenden Befehl aus:

      Sie sehen die folgende Ausgabe:

      Output

      1: Learn Go

      Im letzten Schritt geben Sie Benutzern die Option, Aufgaben aus der Datenbank zu löschen.

      Schritt 8 — Löschen einer Aufgabe

      In diesem Schritt fügen Sie einen neuen Unterbefehl delete hinzu, um Benutzern zu ermöglichen, eine Aufgabe aus der Datenbank zu löschen. Um eine einzelne Aufgabe zu löschen, verwenden Sie die Methode collection.DeleteOne() vom MongoDB-Treiber. Außerdem stützt er sich auf einen Filter, der dem Dokument entspricht, um das Dokument zu löschen.

      Öffnen Sie Ihre main.go-Datei erneut:

      Fügen Sie diese Funktion deleteTask hinzu, um Aufgaben direkt nach Ihrer Funktion getFinished aus der Datenbank zu löschen:

      main.go

      . . .
      func deleteTask(text string) error {
          filter := bson.D{primitive.E{Key: "text", Value: text}}
      
          res, err := collection.DeleteOne(ctx, filter)
          if err != nil {
              return err
          }
      
          if res.DeletedCount == 0 {
              return errors.New("No tasks were deleted")
          }
      
          return nil
      }
      . . .
      

      Diese deleteTask-Methode verwendet ein Zeichenfolgenargument, das das zu löschende Aufgabenelement darstellt. Ein Filter wird so erstellt, dass er mit dem Aufgabenelement übereinstimmt, dessen Texteigenschaft auf das Zeichenfolgenargument festgelegt ist. Sie übergeben den Filter an die DeleteOne()-Methode, die dem Element in der Auflistung entspricht, und löschen es.

      Sie können die DeletedCount-Eigenschaft für das Ergebnis der DeleteOne-Methode überprüfen, um zu bestätigen, ob Dokumente gelöscht wurden. Wenn der Filter nicht mit einem zu löschenden Dokument übereinstimmen kann, ist DeletedCount Null und Sie können in diesem Fall einen Fehler zurückgeben.

      Fügen Sie nun einen neuen Befehl rm wie hervorgehoben hinzu:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Action: func(c *cli.Context) error {
                  tasks, err := getPending()
                  if err != nil {
                      if err == mongo.ErrNoDocuments {
                          fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                          return nil
                      }
      
                      return err
                  }
      
                  printTasks(tasks)
                  return nil
              },
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:    "done",
                      Aliases: []string{"d"},
                      Usage:   "complete a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          return completeTask(text)
                      },
                  },
                  {
                      Name:    "finished",
                      Aliases: []string{"f"},
                      Usage:   "list completed tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getFinished()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `done 'task'` to complete a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:  "rm",
                      Usage: "deletes a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          err := deleteTask(text)
                          if err != nil {
                              return err
                          }
      
                          return nil
                      },
                  },
              }
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      . . .
      

      Wie bei allen anderen zuvor hinzugefügten Unterbefehlen verwendet der Befehl rm sein erstes Argument, um eine Aufgabe in der Datenbank abzugleichen und zu löschen.

      Speichern und schließen Sie Ihre Datei.

      Sie können ausstehende Aufgaben auflisten, indem Sie Ihr Programm ausführen, ohne Unterbefehle zu übergeben:

      Output

      1: Read a book

      Wenn Sie den Unterbefehl rm für die Aufgabe „Buch lesen“ ausführen, wird er aus der Datenbank gelöscht:

      • go run main.go rm "Read a book"

      Wenn Sie alle ausstehenden Aufgaben erneut auflisten, werden Sie feststellen, dass die Aufgabe „Buch lesen“ nicht mehr angezeigt wird, sondern stattdessen eine Aufforderung zum Hinzufügen einer neuen Aufgabe:

      Output

      Nothing to see here Run `add 'task'` to add a task

      In diesem Schritt haben Sie eine Funktion hinzugefügt, um Aufgaben aus der Datenbank zu löschen.

      Zusammenfassung

      Sie haben erfolgreich ein Task-Manager-Befehlszeilenprogramm erstellt und dabei die Grundlagen der Verwendung des MongoDB Go-Treibers erlernt.

      Lesen Sie unbedingt die vollständige Dokumentation zum MongoDB Go-Treiber bei GoDoc, um mehr über die Funktionen zu erfahren, die die Verwendung des Treibers bietet. Die Dokumentation, in der die Verwendung von Aggregationen oder Transaktionen beschrieben wird, könnte für Sie von besonderem Interesse sein.

      Der endgültige Code für dieses Tutorial kann in diesem GitHub repo betrachtet werden.



      Source link

      So installieren und richten Sie WordPress mit LAMP unter Ubuntu 18.04 mithilfe von Ansible ein


      Einführung

      Server-Automatisierung spielt aufgrund der Löschbarkeit von modernen Anwendungsumgebungen eine wesentliche Rolle bei der Systemverwaltung. Konfigurationsverwaltungs-Tools wie Ansible werden üblicherweise verwendet, um den Prozess der automatischen Servereinrichtung zu optimieren, indem Standardverfahren für neue Server festgelegt und menschliche Fehler in Zusammenhang mit manueller Einrichtung reduziert werden.

      Ansible bietet eine einfache Architektur, die keiner speziellen Software für die Installation auf Knoten bedarf. Des Weiteren bietet es eine Reihe stabiler Funktionen und integrierter Module, die das Schreiben von Automatisierungsskripts erleichtern.

      Dieser Leitfaden erklärt, wie Sie die Schritte in unserem Leitfaden So installieren Sie WordPress mit LAMP unter Ubuntu 18.04​​​ automatisieren können. WordPress ist das beliebteste CMS (Content-Management-System) im Internet, das es Benutzern ermöglicht, flexible Blogs und Websites auf einem MySQL-Backend mit PHP-Verarbeitung einzurichten. Nach dem Einrichten kann fast die gesamte Verwaltung vom Web-Frontend aus erfolgen.

      Voraussetzungen

      Für die automatisierte Einrichtung anhand des Playbooks, das wir in diesem Leitfaden besprechen, benötigen Sie Folgendes:

      Gehen Sie zunächst sicher, dass Ihr Ansible-Steuerknoten sich verbinden und Befehle auf Ihrem/Ihren Ansible-Host(s) ausführen kann. Infos zu einem Verbindungstest finden Sie in Schritt 3 unter So installieren und konfigurieren Sie Ansible unter Ubuntu 18.04.

      Welchen Zweck hat dieses Playbook?

      Dieses Ansible-Playbook bietet eine Alternative zur manuellen Ausführung des Verfahrens, das in unserem Leitfaden So installieren Sie WordPress mit LAMP unter Ubuntu 18.04 beschrieben ist.

      Eine Ausführung dieses Playbooks führt die folgenden Aktionen auf Ihren Ansible-Hosts durch:

      1. Installieren Sie aptitude, das von Ansible als Alternative zum Paketmanager apt bevorzugt wird.
      2. Installieren Sie die erforderlichen LAMP-Pakete und PHP-Erweiterungen.
      3. Erstellen und aktivieren Sie einen neuen Apache VirtualHost für die WordPress-Website.
      4. Aktivieren Sie das Apache-Rewrite-Modul (mod_rewrite)​​.
      5. Deaktivieren Sie die Apache-Standard-Website.
      6. Legen Sie das Passwort für den MySQL-Benutzer root fest.
      7. Entfernen Sie anonyme MySQL-Konten und die Testdatenbank.
      8. Erstellen Sie eine neue MySQL-Datenbank und einen Benutzer für die WordPress-Website.
      9. Richten Sie UFW ein, um HTTP-Verkehr auf dem konfigurierten Port (standardmäßig 80) zuzulassen.
      10. Laden Sie WordPress herunter und entpacken Sie es.
      11. Richten Sie eine korrekte Verzeichniseigentümerschaft und korrekte Berechtigungen ein.
      12. Richten Sie die Datei wp-config.php mit der bereitgestellten Vorlage ein.

      Wenn die Ausführung des Playbooks abgeschlossen ist, wird eine WordPress-Installation basierend auf den Optionen, die Sie in Ihren Konfigurationsvariablen definiert haben, auf einer LAMP-Umgebung ausgeführt.

      So verwenden Sie dieses Playbook

      Als Erstes müssen Sie das Playbook WordPress auf LAMP und seine Abhängigkeiten aus dem Repository do-community/ansible-playbooks abrufen. Klonen Sie dieses Repository in einen lokalen Ordner im Ansible-Steuerknoten.

      Falls Sie dieses Repository bereits anhand eines anderen Leitfadens geklont haben, greifen Sie auf Ihre bestehende Kopie von ansible-playbooks​​​ zu und führen Sie den Befehl git pull aus, um sicherzustellen, dass Sie aktualisierten Inhalt haben:

      • cd ~/ansible-playbooks
      • git pull

      Wenn Sie das Repository do-community/ansible-playbooks​​ zum ersten Mal verwenden, klonen Sie zuerst das Repository in Ihren Basisordner mit:

      • cd ~
      • git clone https://github.com/do-community/ansible-playbooks.git
      • cd ansible-playbooks

      Die Dateien, die wir brauchen, befinden sich im Ordner wordpress-lamp_ubuntu1804, der die folgende Struktur hat:

      wordpress-lamp_ubuntu1804
      ├── files
      │   ├── apache.conf.j2
      │   └── wp-config.php.j2
      ├── vars
      │   └── default.yml
      ├── playbook.yml
      └── readme.md
      

      Hier ist eine Beschreibung jeder dieser Dateien:

      • files/apache.conf.j2: Vorlagendatei zur Einrichtung des Apache VirtualHost.
      • files/wp-config.php.j2: Vorlagendatei zum Einrichten der Konfigurationsdatei von WordPress.
      • vars/default.yml: Variablendatei zur Anpassung der Playbook-Einstellungen.
      • playbook.yml: Die Playbook-Datei mit Aufgaben zur Ausführung auf dem/den Remoteserver(n).
      • readme.md: Eine Textdatei mit Informationen über dieses Playbook.

      Bearbeiten Sie die Variablendatei des Playbooks zur Anpassung der Optionen. Greifen Sie auf das Verzeichnis wordpress-lamp_ubuntu1804 zu und öffnen Sie die Datei vars/default.yml mit dem Befehlszeilen-Editor Ihrer Wahl:

      • cd wordpress-lamp_ubuntu1804
      • nano vars/default.yml

      Diese Datei enthält einige Variablen, die Ihrer Aufmerksamkeit bedürfen:

      vars/default.yml

      ---
      #System Settings
      php_modules: [ 'php-curl', 'php-gd', 'php-mbstring', 'php-xml', 'php-xmlrpc', 'php-soap', 'php-intl', 'php-zip' ]
      
      #MySQL Settings
      mysql_root_password: "mysql_root_password"
      mysql_db: "wordpress"
      mysql_user: "sammy"
      mysql_password: "password"
      
      #HTTP Settings
      http_host: "your_domain"
      http_conf: "your_domain.conf"
      http_port: "80"
      

      Die folgende Liste enthält eine kurze Erklärung jeder dieser Variablen und wie Sie diese ändern könnten:

      • php_modules: Ein Array mit PHP-Erweiterungen, die zur Unterstützung Ihrer Einrichtung von WordPress installiert werden sollen. Sie müssen diese Variable nicht ändern, aber Sie könnten neue Erweiterungen in die Liste aufnehmen, wenn Ihre spezifische Einrichtung es erfordert.
      • mysql_root_password: Das gewünschte Passwort für das MySQL-Konto root.
      • mysql_db: Der Name der MySQL-Datenbank, die für WordPress erstellt werden soll.
      • mysql_user​​​: Der Name des MySQL-Benutzers, der für WordPress erstellt werden soll.
      • mysql_password: Das Passwort für den neuen MySQL-Benutzer.
      • http_host: Ihr Domänenname.
      • http_conf: Der Name der Konfigurationsdatei, die in Apache erstellt wird.
      • http_port: HTTP-Port für diesen virtuellen Host, bei dem 80 die Standardeinstellung ist.

      Sobald Sie mit der Aktualisierung der Variablen in vars/default.yml fertig sind, speichern und schließen Sie diese Datei. Wenn Sie nano verwendet haben, drücken Sie STRG+X, Y, dann die EINGABETASTE.

      Sie können dieses Playbook jetzt auf einem oder mehreren Servern ausführen. Die meisten Playbooks werden standardmäßig auf jedem Server in Ihrem Inventar ausgeführt. Sie können das Flag -l verwenden, um sicherzustellen, dass nur eine Teilmenge von Servern oder ein einzelner Server von dem Playbook betroffen ist. Sie können auch das Flag -u verwenden, um anzugeben, welchen Benutzer Sie auf dem Remoteserver für die Verbindung und Ausführung der Playbook-Befehle auf den Remote-Hosts verwenden.

      Um das Playbook nur auf server1 mit Verbindung als sammy auszuführen, können Sie folgenden Befehl verwenden:

      • ansible-playbook playbook.yml -l server1 -u sammy

      Sie erhalten eine Ausgabe, die der folgenden ähnelt:

      Output

      PLAY [all] ***************************************************************************************************************************** TASK [Gathering Facts] ***************************************************************************************************************** ok: [server1] TASK [Install prerequisites] *********************************************************************************************************** ok: [server1] … TASK [Download and unpack latest WordPress] ******************************************************************************************** changed: [server1] TASK [Set ownership] ******************************************************************************************************************* changed: [server1] TASK [Set permissions for directories] ************************************************************************************************* changed: [server1] TASK [Set permissions for files] ******************************************************************************************************* changed: [server1] TASK [Set up wp-config] **************************************************************************************************************** changed: [server1] RUNNING HANDLER [Reload Apache] ******************************************************************************************************** changed: [server1] RUNNING HANDLER [Restart Apache] ******************************************************************************************************* changed: [server1] PLAY RECAP ***************************************************************************************************************************** server1 : ok=22 changed=18 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

      Anmerkung: Weitere Informationen zur Ausführung von Ansible-Playbooks finden Sie in unserem Leitfaden Ansible-Schummelzettel.

      Wenn das Playbook ausgeführt ist, können Sie zu Ihrem Webbrowser gehen, um die Installation von WordPress von dort aus zu beenden.

      Navigieren Sie zu dem Domänennamen oder der öffentlichen IP-Adresse Ihres Servers:

      http://server_host_or_IP
      

      Sie sehen eine Seite wie diese:

      Seite der WordPress-Sprachauswahl

      Nach Auswahl der Sprache, die Sie für Ihre WordPress-Installation verwenden möchten, kommen Sie zum letzten Schritt der Einrichtung Ihres WordPress-Benutzers und -Passworts, damit Sie sich in Ihrem Kontrollfeld anmelden können:

      WordPress-Einrichtung

      Wenn Sie weiter klicken, kommen Sie zu einer Seite, auf der Sie sich anmelden müssen:

      WP-Anmeldeaufforderung

      Nach der Anmeldung gelangen Sie zum WordPress-Administrations-Dashboard:

      WP-Adminbereich

      Einige gängige Schritte zur Anpassung Ihrer WordPress-Installation sind u. a. die Auswahl der Permalink-Einstellung für Ihre Beiträge (in Einstellungen > Permalinks) und die Wahl eines neuen Designs (in Darstellung > Design).

      Der Inhalt des Playbooks

      Sie finden die Server-Einrichtung für WordPress auf LAMP in diesem Tutorial im Ordner wordpress-lamp_ubuntu1804​​​ im Repository DigitalOcean Community Playbooks. Klicken Sie zum direkten Kopieren oder Herunterladen des Skript-Inhalts auf die Schaltfläche Raw im oberen Bereich jedes Skripts.

      Der vollständige Inhalt des Playbooks sowie seine zugehörigen Dateien finden Sie ebenfalls hier.

      vars/default.yml

      Die Variablendatei default.yml enthält Werte, die in den Playbook-Aufgaben verwendet werden, wie z. B. Datenbank-Einstellungen und den Domänennamen für die Konfiguration mit Apache.

      vars/default.yml

      #System Settings
      php_modules: [ 'php-curl', 'php-gd', 'php-mbstring', 'php-xml', 'php-xmlrpc', 'php-soap', 'php-intl', 'php-zip' ]
      
      #MySQL Settings
      mysql_root_password: "mysql_root_password"
      mysql_db: "wordpress"
      mysql_user: "sammy"
      mysql_password: "password"
      
      #HTTP Settings
      http_host: "your_domain"
      http_conf: "your_domain.conf"
      http_port: "80"
      

      files/apache.conf.j2

      Die Datei apache.conf.j2 ist eine Jinja 2-Vorlagendatei, die einen neuen Apache VirtualHost konfiguriert. Die in dieser Vorlage verwendeten Variablen sind in der Variablendatei vars/default.yml definiert.

      files/apache.conf.j2

      <VirtualHost *:{{ http_port }}>
         ServerAdmin webmaster@localhost
         ServerName {{ http_host }}
         ServerAlias www.{{ http_host }}
         DocumentRoot /var/www/{{ http_host }}
         ErrorLog ${APACHE_LOG_DIR}/error.log
         CustomLog ${APACHE_LOG_DIR}/access.log combined
      
         <Directory /var/www/{{ http_host }}>
               Options -Indexes
         </Directory>
      
         <IfModule mod_dir.c>
             DirectoryIndex index.php index.html index.cgi index.pl  index.xhtml index.htm
         </IfModule>
      
      </VirtualHost>
      

      files/wp-config.php.j2

      Die Datei wp-config.php.j2 ist eine weitere Jinja-Vorlage für die Einrichtung der Hauptkonfigurationsdatei, die von WordPress verwendet wird. Die in dieser Vorlage verwendeten Variablen sind in der Variablendatei vars/default.yml definiert. Eindeutige Authentifizierungsschlüssel und Salts werden mit einer Hash-Funktion generiert.

      files/info.php.j2

      <?php
      /**
       * The base configuration for WordPress
       *
       * The wp-config.php creation script uses this file during the
       * installation. You don't have to use the web site, you can
       * copy this file to "wp-config.php" and fill in the values.
       *
       * This file contains the following configurations:
       *
       * * MySQL settings
       * * Secret keys
       * * Database table prefix
       * * ABSPATH
       *
       * @link https://codex.wordpress.org/Editing_wp-config.php
       *
       * @package WordPress
       */
      
      // ** MySQL settings - You can get this info from your web host ** //
      /** The name of the database for WordPress */
      define( 'DB_NAME', '{{ mysql_db }}' );
      
      /** MySQL database username */
      define( 'DB_USER', '{{ mysql_user }}' );
      
      /** MySQL database password */
      define( 'DB_PASSWORD', '{{ mysql_password }}' );
      
      /** MySQL hostname */
      define( 'DB_HOST', 'localhost' );
      
      /** Database Charset to use in creating database tables. */
      define( 'DB_CHARSET', 'utf8' );
      
      /** The Database Collate type. Don't change this if in doubt. */
      define( 'DB_COLLATE', '' );
      
      /** Filesystem access **/
      define('FS_METHOD', 'direct');
      
      /**#@+
       * Authentication Unique Keys and Salts.
       *
       * Change these to different unique phrases!
       * You can generate these using the {@link https://api.wordpress.org/secret-key/1.1/salt/ WordPress.org secret-key service}
       * You can change these at any point in time to invalidate all existing cookies. This will force all users to have to log in again.
       *
       * @since 2.6.0
       */
      define( 'AUTH_KEY',         '{{ lookup('password', '/dev/null chars=ascii_letters length=64') }}' );
      define( 'SECURE_AUTH_KEY',  '{{ lookup('password', '/dev/null chars=ascii_letters length=64') }}' );
      define( 'LOGGED_IN_KEY',    '{{ lookup('password', '/dev/null chars=ascii_letters length=64') }}' );
      define( 'NONCE_KEY',        '{{ lookup('password', '/dev/null chars=ascii_letters length=64') }}' );
      define( 'AUTH_SALT',        '{{ lookup('password', '/dev/null chars=ascii_letters length=64') }}' );
      define( 'SECURE_AUTH_SALT', '{{ lookup('password', '/dev/null chars=ascii_letters length=64') }}' );
      define( 'LOGGED_IN_SALT',   '{{ lookup('password', '/dev/null chars=ascii_letters length=64') }}' );
      define( 'NONCE_SALT',       '{{ lookup('password', '/dev/null chars=ascii_letters length=64') }}' );
      
      /**#@-*/
      
      /**
       * WordPress Database Table prefix.
       *
       * You can have multiple installations in one database if you give each
       * a unique prefix. Only numbers, letters, and underscores please!
       */
      $table_prefix = 'wp_';
      
      /**
       * For developers: WordPress debugging mode.
       *
       * Change this to true to enable the display of notices during development.
       * It is strongly recommended that plugin and theme developers use WP_DEBUG
       * in their development environments.
       *
       * For information on other constants that can be used for debugging,
       * visit the Codex.
       *
       * @link https://codex.wordpress.org/Debugging_in_WordPress
       */
      define( 'WP_DEBUG', false );
      
      /* That's all, stop editing! Happy publishing. */
      
      /** Absolute path to the WordPress directory. */
      if ( ! defined( 'ABSPATH' ) ) {
          define( 'ABSPATH', dirname( __FILE__ ) . '/' );
      }
      
      /** Sets up WordPress vars and included files. */
      require_once( ABSPATH . 'wp-settings.php' );
      
      

      playbook.yml

      Alle Aufgaben dieser Einrichtung sind in der Datei playbook.yml definiert. Am Anfang werden die Gruppen von Servern definiert, die das Ziel dieser Einrichtung (all) sein sollen. Anschließend wird mit become: true definiert, dass Aufgaben standardmäßig mit Rechteausweitung (sudo) ausgeführt werden sollen. Dann enthält es die Variablendatei vars/default.yml, um Konfigurationsoptionen zu laden.

      playbook.yml

      ---
      - hosts: all
        become: true
        vars_files:
          - vars/default.yml
      
        tasks:
          - name: Install prerequisites
            apt: name=aptitude update_cache=yes state=latest force_apt_get=yes
            tags: [ system ]
      
          - name: Install LAMP Packages
            apt: name={{ item }} update_cache=yes state=latest
            loop: [ 'apache2', 'mysql-server', 'python3-pymysql', 'php', 'php-mysql', 'libapache2-mod-php' ]
            tags: [ system ]
      
          - name: Install PHP Extensions
            apt: name={{ item }} update_cache=yes state=latest
            loop: "{{ php_modules }}"
            tags: [ system ]
      
        # Apache Configuration
          - name: Create document root
            file:
              path: "/var/www/{{ http_host }}"
              state: directory
              owner: "www-data"
              group: "www-data"
              mode: '0755'
            tags: [ apache ]
      
          - name: Set up Apache VirtualHost
            template:
              src: "files/apache.conf.j2"
              dest: "/etc/apache2/sites-available/{{ http_conf }}"
            notify: Reload Apache
            tags: [ apache ]
      
          - name: Enable rewrite module
            shell: /usr/sbin/a2enmod rewrite
            notify: Reload Apache
            tags: [ apache ]
      
          - name: Enable new site
            shell: /usr/sbin/a2ensite {{ http_conf }}
            notify: Reload Apache
            tags: [ apache ]
      
          - name: Disable default Apache site
            shell: /usr/sbin/a2dissite 000-default.conf
            notify: Restart Apache
            tags: [ apache ]
      
        # MySQL Configuration
          - name: Set the root password
            mysql_user:
              name: root
              password: "{{ mysql_root_password }}"
              login_unix_socket: /var/run/mysqld/mysqld.sock
            tags: [ mysql, mysql-root ]
      
          - name: Remove all anonymous user accounts
            mysql_user:
              name: ''
              host_all: yes
              state: absent
              login_user: root
              login_password: "{{ mysql_root_password }}"
            tags: [ mysql ]
      
          - name: Remove the MySQL test database
            mysql_db:
              name: test
              state: absent
              login_user: root
              login_password: "{{ mysql_root_password }}"
            tags: [ mysql ]
      
          - name: Creates database for WordPress
            mysql_db:
              name: "{{ mysql_db }}"
              state: present
              login_user: root
              login_password: "{{ mysql_root_password }}"
            tags: [ mysql ]
      
          - name: Create MySQL user for WordPress
            mysql_user:
              name: "{{ mysql_user }}"
              password: "{{ mysql_password }}"
              priv: "{{ mysql_db }}.*:ALL"
              state: present
              login_user: root
              login_password: "{{ mysql_root_password }}"
            tags: [ mysql ]
      
        # UFW Configuration
          - name: "UFW - Allow HTTP on port {{ http_port }}"
            ufw:
              rule: allow
              port: "{{ http_port }}"
              proto: tcp
            tags: [ system ]
      
        # WordPress Configuration
          - name: Download and unpack latest WordPress
            unarchive:
              src: https://wordpress.org/latest.tar.gz
              dest: "/var/www/{{ http_host }}"
              remote_src: yes
              creates: "/var/www/{{ http_host }}/wordpress"
            tags: [ wordpress ]
      
          - name: Set ownership
            file:
              path: "/var/www/{{ http_host }}"
              state: directory
              recurse: yes
              owner: www-data
              group: www-data
            tags: [ wordpress ]
      
          - name: Set permissions for directories
            shell: "/usr/bin/find /var/www/{{ http_host }}/wordpress/ -type d -exec chmod 750 {} \;"
            tags: [ wordpress ]
      
          - name: Set permissions for files
            shell: "/usr/bin/find /var/www/{{ http_host }}/wordpress/ -type f -exec chmod 640 {} \;"
            tags: [ wordpress ]
      
          - name: Set up wp-config
            template:
              src: "files/wp-config.php.j2"
              dest: "/var/www/{{ http_host }}/wordpress/wp-config.php"
            tags: [ wordpress ]
      
        handlers:
          - name: Reload Apache
            service:
              name: apache2
              state: reloaded
      
          - name: Restart Apache
            service:
              name: apache2
              state: restarted
      

      Sie können diese Dateien an Ihre individuellen Bedürfnisse in Ihrem Workflow anpassen.

      Zusammenfassung

      In diesem Leitfaden haben wir den Prozess der Installation und Einrichtung einer WordPress-Website mit LAMP auf einem Ubuntu-18.04-Server automatisiert.

      Wenn Sie weitere Aufgaben in dieses Playbook aufnehmen möchten, um Ihre Servereinrichtung weiter anzupassen, finden Sie diesbezügliche Informationen in unserer Ansible-Einführung Grundlagen des Konfigurationsmanagements: Ansible-Playbooks schreiben.



      Source link