Wypróbowanie nowych narzędzi do budowania i automatyzacji wdrażania w Kubernetes

Wypróbowanie nowych narzędzi do budowania i automatyzacji wdrażania w Kubernetes

Cześć! Niedawno wydano wiele fajnych narzędzi do automatyzacji, zarówno do tworzenia obrazów Dockera, jak i do wdrażania w Kubernetes. W związku z tym postanowiłem pobawić się z GitLabem, dokładnie przestudiować jego możliwości i oczywiście skonfigurować potok.

Inspiracją do tej pracy była strona internetowa kubernetes.io, z którego powstaje kody źródłowe automatycznie, a dla każdego wysłanego żądania puli robot automatycznie generuje wersję poglądową witryny z Twoimi zmianami i udostępnia link do przeglądania.

Próbowałem zbudować podobny proces od podstaw, ale w całości oparty na Gitlab CI i bezpłatnych narzędziach, których używam do wdrażania aplikacji na Kubernetesie. Dzisiaj w końcu opowiem Wam o nich więcej.

W artykule zostaną omówione narzędzia takie jak:
Hugo, qbec, kaniko, git-krypta и GitLab CI z tworzeniem dynamicznych środowisk.

Spis treści

  1. Poznaj Hugo
  2. Przygotowanie pliku Dockerfile
  3. Poznajemy Kaniko
  4. Poznajemy qbeca
  5. Próbuję Gitlab-runner z Kubernetes-executorem
  6. Wdrażanie wykresów Helm za pomocą qbec
  7. Przedstawiamy git-crypt
  8. Tworzenie obrazu przybornika
  9. Nasz pierwszy potok i montaż obrazów według tagów
  10. Automatyzacja wdrażania
  11. Artefakty i montaż podczas naciskania na mistrza
  12. Dynamiczne środowiska
  13. Przejrzyj aplikacje

1. Poznanie Hugo

Jako przykład naszego projektu postaramy się stworzyć witrynę do publikowania dokumentacji zbudowaną na platformie Hugo. Hugo to generator treści statycznych.

Dla tych, którzy nie są zaznajomieni z generatorami statycznymi, opowiem o nich trochę więcej. W przeciwieństwie do konwencjonalnych silników stron internetowych z bazą danych i odrobiną PHP, które na żądanie użytkownika generują strony w locie, generatory statyczne są zaprojektowane nieco inaczej. Umożliwiają pobranie źródeł, zwykle zestawu plików w znacznikach Markdown i szablonów motywów, a następnie skompilowanie ich w całkowicie gotową stronę internetową.

Oznacza to, że w rezultacie otrzymasz strukturę katalogów i zestaw wygenerowanych plików HTML, które możesz po prostu przesłać na dowolny tani hosting i uzyskać działającą stronę internetową.

Możesz zainstalować Hugo lokalnie i wypróbować:

Inicjowanie nowej witryny:

hugo new site docs.example.org

A jednocześnie repozytorium git:

cd docs.example.org
git init

Póki co nasza strona jest nieskazitelna i żeby coś się na niej pojawiło musimy najpierw podłączyć motyw, motyw to po prostu zestaw szablonów i określonych reguł według których generowana jest nasza strona.

Dla tematu, którego użyjemy Uczyć się, który moim zdaniem doskonale nadaje się na stronę z dokumentacją.

Chciałbym zwrócić szczególną uwagę na to, że nie musimy zapisywać plików motywu w naszym repozytorium projektu, zamiast tego możemy po prostu połączyć go za pomocą podmoduł git:

git submodule add https://github.com/matcornic/hugo-theme-learn themes/learn

Tym samym nasze repozytorium będzie zawierało jedynie pliki bezpośrednio związane z naszym projektem, a powiązany motyw pozostanie jako link do konkretnego repozytorium i w nim zatwierdzenie, czyli zawsze można go pobrać z oryginalnego źródła i nie bać się niekompatybilne zmiany.

Poprawmy konfigurację konfiguracja.toml:

baseURL = "http://docs.example.org/"
languageCode = "en-us"
title = "My Docs Site"
theme = "learn"

Już na tym etapie możesz uruchomić:

hugo server

I pod adresem http://localhost:1313/ sprawdź naszą nowo utworzoną stronę internetową, wszystkie zmiany dokonane w katalogu automatycznie aktualizują otwartą stronę w przeglądarce, bardzo wygodne!

Spróbujmy stworzyć stronę tytułową w treść/_index.md:

# My docs site

## Welcome to the docs!

You will be very smart :-)

Zrzut ekranu nowo utworzonej strony

Wypróbowanie nowych narzędzi do budowania i automatyzacji wdrażania w Kubernetes

Aby wygenerować witrynę, po prostu uruchom:

hugo

Zawartość katalogu publiczny/ i będzie Twoją stroną internetową.
Tak, nawiasem mówiąc, od razu dodajmy to do gitignore:

echo /public > .gitignore

Nie zapomnij zatwierdzić naszych zmian:

git add .
git commit -m "New site created"

2. Przygotowanie pliku Dockerfile

Czas zdefiniować strukturę naszego repozytorium. Zwykle używam czegoś takiego:

.
├── deploy
│   ├── app1
│   └── app2
└── dockerfiles
    ├── image1
    └── image2

  • pliki dokowane/ — zawierają katalogi z plikami Docker i wszystkim, co niezbędne do budowania naszych obrazów Docker.
  • wdrożyć/ — zawiera katalogi do wdrażania naszych aplikacji na Kubernetesie

W ten sposób utworzymy nasz pierwszy plik Dockerfile wzdłuż ścieżki dockerfiles/witryna internetowa/Dockerfile

FROM alpine:3.11 as builder
ARG HUGO_VERSION=0.62.0
RUN wget -O- https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_linux-64bit.tar.gz | tar -xz -C /usr/local/bin
ADD . /src
RUN hugo -s /src

FROM alpine:3.11
RUN apk add --no-cache darkhttpd
COPY --from=builder /src/public /var/www
ENTRYPOINT [ "/usr/bin/darkhttpd" ]
CMD [ "/var/www" ]

Jak widać, plik Dockerfile zawiera dwa OD, ta funkcja nazywa się budowa wieloetapowa i pozwala wykluczyć wszystko, co niepotrzebne z końcowego obrazu Dockera.
Zatem ostateczny obraz będzie zawierał tylko ciemnyhttpd (lekki serwer HTTP) i publiczny/ — zawartość naszej statycznie generowanej strony internetowej.

Nie zapomnij zatwierdzić naszych zmian:

git add dockerfiles/website
git commit -m "Add Dockerfile for website"

3. Poznanie kaniko

Jako konstruktor obrazów dokowanych zdecydowałem się użyć kaniko, ponieważ jego działanie nie wymaga demona dokującego, a samą kompilację można przeprowadzić na dowolnej maszynie, a pamięć podręczną można przechowywać bezpośrednio w rejestrze, eliminując w ten sposób potrzebę posiadania pełnoprawnego trwałego magazynu.

Aby zbudować obraz, po prostu uruchom kontener za pomocą wykonawca kaniko i przekaż mu bieżący kontekst kompilacji; można to również zrobić lokalnie za pomocą okna dokowanego:

docker run -ti --rm 
  -v $PWD:/workspace 
  -v ~/.docker/config.json:/kaniko/.docker/config.json:ro 
  gcr.io/kaniko-project/executor:v0.15.0 
  --cache 
  --dockerfile=dockerfiles/website/Dockerfile 
  --destination=registry.gitlab.com/kvaps/docs.example.org/website:v0.0.1

Где http://rejestracja.gitlab.com/kvaps/docs.example.org/website — nazwa obrazu dokera; po zbudowaniu zostanie on automatycznie uruchomiony w rejestrze dokera.

Parametr --Pamięć podręczna pozwala na buforowanie warstw w rejestrze dokera; w podanym przykładzie zostaną one zapisane w http://register.gitlab.com/kvaps/docs.example.org/website/cache, ale możesz określić inną ścieżkę za pomocą parametru --repo-cache.

Zrzut ekranu rejestru dokerów

Wypróbowanie nowych narzędzi do budowania i automatyzacji wdrażania w Kubernetes

4. Poznanie qbeca

Qbec to narzędzie do wdrażania, które pozwala deklaratywnie opisać manifesty aplikacji i wdrożyć je w Kubernetes. Użycie Jsonnet jako głównej składni pozwala znacznie uprościć opis różnic pomiędzy wieloma środowiskami, a także niemal całkowicie eliminuje powtarzanie kodu.

Może to być szczególnie prawdziwe w przypadkach, gdy musisz wdrożyć aplikację w kilku klastrach o różnych parametrach i chcesz deklaratywnie opisać je w Git.

Qbec umożliwia także renderowanie wykresów Helma poprzez przekazanie im niezbędnych parametrów, a następnie operowanie nimi w taki sam sposób jak zwykłe manifesty, w tym można nanosić na nie różne mutacje, a to z kolei pozwala pozbyć się konieczności użyj ChartMuseum. Oznacza to, że możesz przechowywać i renderować wykresy bezpośrednio z git, tam gdzie ich miejsce.

Jak powiedziałem wcześniej, wszystkie wdrożenia będziemy przechowywać w katalogu wdrożyć/:

mkdir deploy
cd deploy

Zainicjujmy naszą pierwszą aplikację:

qbec init website
cd website

Teraz struktura naszej aplikacji wygląda następująco:

.
├── components
├── environments
│   ├── base.libsonnet
│   └── default.libsonnet
├── params.libsonnet
└── qbec.yaml

spójrzmy na plik qbec.yaml:

apiVersion: qbec.io/v1alpha1
kind: App
metadata:
  name: website
spec:
  environments:
    default:
      defaultNamespace: docs
      server: https://kubernetes.example.org:8443
  vars: {}

Tutaj nas przede wszystkim interesuje specyficzne środowiska, qbec stworzył już dla nas domyślne środowisko i pobrał adres serwera oraz przestrzeń nazw z naszego obecnego kubeconfig.
Teraz podczas wdrażania do domyślnym środowisku, qbec zawsze będzie wdrażał tylko w określonym klastrze Kubernetes i w określonej przestrzeni nazw, co oznacza, że ​​nie musisz już przełączać się między kontekstami i przestrzeniami nazw, aby przeprowadzić wdrożenie.
W razie potrzeby zawsze możesz zaktualizować ustawienia w tym pliku.

Wszystkie Twoje środowiska są opisane w qbec.yamli w pliku params.libsonnet, gdzie jest napisane, skąd pobrać dla nich parametry.

Następnie widzimy dwa katalogi:

  • komponenty / — tutaj będą przechowywane wszystkie manifesty naszej aplikacji; można je opisać zarówno w plikach jsonnet, jak i zwykłych plikach yaml
  • środowiska/ — tutaj opiszemy wszystkie zmienne (parametry) dla naszych środowisk.

Domyślnie mamy dwa pliki:

  • środowiska/base.libsonnet - będzie zawierał wspólne parametry dla wszystkich środowisk
  • środowiska/default.libsonnet — zawiera parametry nadpisane dla środowiska domyślnym

Otwórzmy środowiska/base.libsonnet i dodaj tam parametry naszego pierwszego komponentu:

{
  components: {
    website: {
      name: 'example-docs',
      image: 'registry.gitlab.com/kvaps/docs.example.org/website:v0.0.1',
      replicas: 1,
      containerPort: 80,
      servicePort: 80,
      nodeSelector: {},
      tolerations: [],
      ingressClass: 'nginx',
      domain: 'docs.example.org',
    },
  },
}

Stwórzmy także nasz pierwszy komponent komponenty/strona internetowa.jsonnet:

local env = {
  name: std.extVar('qbec.io/env'),
  namespace: std.extVar('qbec.io/defaultNs'),
};
local p = import '../params.libsonnet';
local params = p.components.website;

[
  {
    apiVersion: 'apps/v1',
    kind: 'Deployment',
    metadata: {
      labels: { app: params.name },
      name: params.name,
    },
    spec: {
      replicas: params.replicas,
      selector: {
        matchLabels: {
          app: params.name,
        },
      },
      template: {
        metadata: {
          labels: { app: params.name },
        },
        spec: {
          containers: [
            {
              name: 'darkhttpd',
              image: params.image,
              ports: [
                {
                  containerPort: params.containerPort,
                },
              ],
            },
          ],
          nodeSelector: params.nodeSelector,
          tolerations: params.tolerations,
          imagePullSecrets: [{ name: 'regsecret' }],
        },
      },
    },
  },
  {
    apiVersion: 'v1',
    kind: 'Service',
    metadata: {
      labels: { app: params.name },
      name: params.name,
    },
    spec: {
      selector: {
        app: params.name,
      },
      ports: [
        {
          port: params.servicePort,
          targetPort: params.containerPort,
        },
      ],
    },
  },
  {
    apiVersion: 'extensions/v1beta1',
    kind: 'Ingress',
    metadata: {
      annotations: {
        'kubernetes.io/ingress.class': params.ingressClass,
      },
      labels: { app: params.name },
      name: params.name,
    },
    spec: {
      rules: [
        {
          host: params.domain,
          http: {
            paths: [
              {
                backend: {
                  serviceName: params.name,
                  servicePort: params.servicePort,
                },
              },
            ],
          },
        },
      ],
    },
  },
]

W tym pliku opisaliśmy jednocześnie trzy encje Kubernetes, są to: Rozlokowanie, Usługi и Ingres. Gdybyśmy chcieli, moglibyśmy poukładać je w różne komponenty, ale na tym etapie wystarczy nam jeden.

składnia jsonnet jest bardzo podobny do zwykłego jsona, w zasadzie zwykły json jest już poprawnym jsonnetem, więc na początku może być łatwiej ci korzystać z usług online, takich jak yaml2json aby przekonwertować zwykły yaml na json lub, jeśli twoje komponenty nie zawierają żadnych zmiennych, można je opisać w postaci zwykłego yaml.

Podczas pracy z jsonnet Gorąco polecam zainstalowanie wtyczki do edytora

Na przykład istnieje wtyczka do vima vim-jsonnet, który włącza podświetlanie składni i wykonuje się automatycznie jsonnet fmt za każdym razem, gdy zapisujesz (wymaga zainstalowanego jsonnet).

Wszystko gotowe, teraz możemy przystąpić do wdrażania:

Aby zobaczyć, co mamy, uruchommy:

qbec show default

Na wyjściu zobaczysz wyrenderowane manifesty YAML, które zostaną zastosowane do domyślnego klastra.

Świetnie, teraz zastosuj:

qbec apply default

Na wyjściu zawsze zobaczysz, co zostanie zrobione w Twoim klastrze, qbec poprosi Cię o zgodę na zmiany wpisując y będziesz mógł potwierdzić swoje intencje.

Nasza aplikacja jest gotowa i wdrożona!

Jeśli dokonasz zmian, zawsze możesz:

qbec diff default

aby zobaczyć, jak te zmiany wpłyną na bieżące wdrożenie

Nie zapomnij zatwierdzić naszych zmian:

cd ../..
git add deploy/website
git commit -m "Add deploy for website"

5. Próbowanie Gitlab-runnera z Kubernetes-executorem

Do niedawna używałem tylko zwykłego gitlab-runner na wstępnie przygotowanej maszynie (kontener LXC) z powłoką lub modułem dokującym. Początkowo w naszym gitlabie zdefiniowaliśmy kilka takich modułów biegaczy na całym świecie. Zebrali obrazy dokerów dla wszystkich projektów.

Ale jak pokazała praktyka, ta opcja nie jest najbardziej idealna, zarówno pod względem praktyczności, jak i bezpieczeństwa. O wiele lepiej i ideologicznie bardziej poprawne jest posiadanie osobnych modułów uruchamiających dla każdego projektu, a nawet dla każdego środowiska.

Na szczęście nie stanowi to żadnego problemu, ponieważ teraz będziemy wdrażać gitlab-runner bezpośrednio w ramach naszego projektu bezpośrednio w Kubernetesie.

Gitlab udostępnia gotowy wykres steru do wdrażania gitlab-runner w Kubernetes. Więc wszystko, co musisz zrobić, to się dowiedzieć token rejestracji dla naszego projektu w Ustawienia -> CI / CD -> Biegacze i przekaż go sterowi:

helm repo add gitlab https://charts.gitlab.io

helm install gitlab-runner 
  --set gitlabUrl=https://gitlab.com 
  --set runnerRegistrationToken=yga8y-jdCusVDn_t4Wxc 
  --set rbac.create=true 
  gitlab/gitlab-runner

Gdzie:

  • https://gitlab.com — adres Twojego serwera Gitlab.
  • yga8y-jdCusVDn_t4Wxc — token rejestracyjny dla Twojego projektu.
  • rbac.create=true — zapewnia biegaczowi niezbędną ilość uprawnień, aby móc tworzyć pody do wykonywania naszych zadań za pomocą kubernetes-executor.

Jeśli wszystko zostało wykonane poprawnie, w sekcji powinieneś zobaczyć zarejestrowanego biegacza Biegaczew ustawieniach projektu.

Zrzut ekranu dodanego biegacza

Wypróbowanie nowych narzędzi do budowania i automatyzacji wdrażania w Kubernetes

Czy to takie proste? - tak, to takie proste! Koniec z ręczną rejestracją biegaczy, od teraz biegacze będą tworzeni i niszczeni automatycznie.

6. Wdrażaj wykresy Helm za pomocą QBEC

Ponieważ zdecydowaliśmy się rozważyć gitlab-runner część naszego projektu, czas opisać ją w naszym repozytorium Git.

Moglibyśmy opisać to jako oddzielny element , ale w przyszłości planujemy wdrożyć różne kopie bardzo często, w przeciwieństwie do gitlab-runner, który zostanie wdrożony tylko raz na klaster Kubernetes. Zainicjujmy więc dla niego osobną aplikację:

cd deploy
qbec init gitlab-runner
cd gitlab-runner

Tym razem nie będziemy opisywać ręcznie encji Kubernetesa, lecz skorzystamy z gotowego wykresu Helma. Jedną z zalet qbec jest możliwość renderowania wykresów Helma bezpośrednio z repozytorium Git.

Połączmy to za pomocą podmodułu git:

git submodule add https://gitlab.com/gitlab-org/charts/gitlab-runner vendor/gitlab-runner

Teraz katalog sprzedawca/gitlab-runner Mamy repozytorium z wykresem dla gitlab-runner.

W podobny sposób możesz połączyć inne repozytoria, np. całe repozytorium z oficjalnymi wykresami https://github.com/helm/charts

Opiszmy komponent komponenty/gitlab-runner.jsonnet:

local env = {
  name: std.extVar('qbec.io/env'),
  namespace: std.extVar('qbec.io/defaultNs'),
};
local p = import '../params.libsonnet';
local params = p.components.gitlabRunner;

std.native('expandHelmTemplate')(
  '../vendor/gitlab-runner',
  params.values,
  {
    nameTemplate: params.name,
    namespace: env.namespace,
    thisFile: std.thisFile,
    verbose: true,
  }
)

Pierwszy argument za rozwiń szablon hełmu następnie przekazujemy ścieżkę do wykresu wartości parametrów, które pobieramy z parametrów środowiska, następnie przychodzi obiekt

  • szablon nazwy — tytuł wydania
  • przestrzeń nazw — przestrzeń nazw przeniesiona do steru
  • ten plik — wymagany parametr przekazujący ścieżkę do bieżącego pliku
  • gadatliwy - pokazuje polecenie szablon steru ze wszystkimi argumentami podczas renderowania wykresu

Teraz opiszemy parametry naszego komponentu w środowiska/base.libsonnet:

local secrets = import '../secrets/base.libsonnet';

{
  components: {
    gitlabRunner: {
      name: 'gitlab-runner',
      values: {
        gitlabUrl: 'https://gitlab.com/',
        rbac: {
          create: true,
        },
        runnerRegistrationToken: secrets.runnerRegistrationToken,
      },
    },
  },
}

Zauważyć biegaczToken Rejestracyjny pobieramy z pliku zewnętrznego sekrety/base.libsonnet, utwórzmy to:

{
  runnerRegistrationToken: 'yga8y-jdCusVDn_t4Wxc',
}

Sprawdźmy, czy wszystko działa:

qbec show default

jeśli wszystko jest w porządku, możemy usunąć naszą wcześniej wdrożoną wersję za pośrednictwem Helma:

helm uninstall gitlab-runner

i wdróż go w ten sam sposób, ale poprzez qbec:

qbec apply default

7. Wprowadzenie do git-crypt

Git-krypta to narzędzie, które pozwala ustawić przejrzyste szyfrowanie dla Twojego repozytorium.

W tej chwili nasza struktura katalogów dla gitlab-runner wygląda następująco:

.
├── components
│   ├── gitlab-runner.jsonnet
├── environments
│   ├── base.libsonnet
│   └── default.libsonnet
├── params.libsonnet
├── qbec.yaml
├── secrets
│   └── base.libsonnet
└── vendor
    └── gitlab-runner (submodule)

Ale przechowywanie sekretów w Git nie jest bezpieczne, prawda? Musimy więc je odpowiednio zaszyfrować.

Zwykle ze względu na jedną zmienną nie zawsze ma to sens. Możesz przenieść sekrety do qbec oraz poprzez zmienne środowiskowe systemu CI.
Warto jednak zauważyć, że istnieją również bardziej złożone projekty, które mogą zawierać znacznie więcej tajemnic; przeniesienie ich wszystkich poprzez zmienne środowiskowe będzie niezwykle trudne.

Co więcej, w tym przypadku nie byłbym w stanie opowiedzieć Ci o tak wspaniałym narzędziu jak git-krypta.

git-krypta Jest to również wygodne o tyle, że pozwala na zapisanie całej historii sekretów, a także porównywanie, łączenie i rozwiązywanie konfliktów w taki sam sposób, w jaki jesteśmy przyzwyczajeni do tego w przypadku Gita.

Pierwsza rzecz po montażu git-krypta musimy wygenerować klucze do naszego repozytorium:

git crypt init

Jeśli posiadasz klucz PGP, możesz od razu dodać siebie jako współpracownika do tego projektu:

git-crypt add-gpg-user [email protected]

W ten sposób zawsze będziesz mógł odszyfrować to repozytorium przy użyciu swojego klucza prywatnego.

Jeśli nie masz klucza PGP i nie spodziewasz się tego, możesz pójść w drugą stronę i wyeksportować klucz projektu:

git crypt export-key /path/to/keyfile

Zatem każdy, kto ma wyeksportowany plik plik klucza będzie mógł odszyfrować Twoje repozytorium.

Czas odkryć nasz pierwszy sekret.
Przypominam, że nadal jesteśmy w katalogu wdrożyć/gitlab-runner/, gdzie mamy katalog tajniki/, zaszyfrujmy w nim wszystkie zawarte w nim pliki, w tym celu utworzymy plik sekrety/.gitattributes o następującej treści:

* filter=git-crypt diff=git-crypt
.gitattributes !filter !diff

Jak widać z treści, wszystkie pliki są zamaskowane * zostanie przejechany git-krypta, z wyjątkiem większości .gitattributes

Możemy to sprawdzić uruchamiając:

git crypt status -e

Wynikiem będzie lista wszystkich plików w repozytorium, dla których włączone jest szyfrowanie

To wszystko, teraz możemy bezpiecznie zatwierdzić nasze zmiany:

cd ../..
git add .
git commit -m "Add deploy for gitlab-runner"

Aby zablokować repozytorium, po prostu uruchom:

git crypt lock

i natychmiast wszystkie zaszyfrowane pliki zamienią się w coś binarnego, nie będzie można ich odczytać.
Aby odszyfrować repozytorium, uruchom:

git crypt unlock

8. Utwórz obraz przybornika

Obraz przybornika to obraz zawierający wszystkie narzędzia, których będziemy używać do wdrażania naszego projektu. Będzie używany przez moduł uruchamiający Gitlab do wykonywania typowych zadań wdrożeniowych.

Tutaj wszystko jest proste, stwórzmy nowe dockerfiles/toolbox/plikdockerfile o następującej treści:

FROM alpine:3.11

RUN apk add --no-cache git git-crypt

RUN QBEC_VER=0.10.3 
 && wget -O- https://github.com/splunk/qbec/releases/download/v${QBEC_VER}/qbec-linux-amd64.tar.gz 
     | tar -C /tmp -xzf - 
 && mv /tmp/qbec /tmp/jsonnet-qbec /usr/local/bin/

RUN KUBECTL_VER=1.17.0 
 && wget -O /usr/local/bin/kubectl 
      https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_VER}/bin/linux/amd64/kubectl 
 && chmod +x /usr/local/bin/kubectl

RUN HELM_VER=3.0.2 
 && wget -O- https://get.helm.sh/helm-v${HELM_VER}-linux-amd64.tar.gz 
     | tar -C /tmp -zxf - 
 && mv /tmp/linux-amd64/helm /usr/local/bin/helm

Jak widać, na tym obrazku instalujemy wszystkie narzędzia, których użyliśmy do wdrożenia naszej aplikacji. Nie potrzebujemy tego tutaj, chyba że kubectl, ale możesz chcieć się nim pobawić na etapie konfiguracji potoku.

Ponadto, aby móc komunikować się z Kubernetesem i wdrażać na nim, musimy skonfigurować rolę dla podów generowanych przez gitlab-runner.

W tym celu przejdźmy do katalogu z gitlab-runnerem:

cd deploy/gitlab-runner

i dodaj nowy komponent komponenty/rbac.jsonnet:

local env = {
  name: std.extVar('qbec.io/env'),
  namespace: std.extVar('qbec.io/defaultNs'),
};
local p = import '../params.libsonnet';
local params = p.components.rbac;

[
  {
    apiVersion: 'v1',
    kind: 'ServiceAccount',
    metadata: {
      labels: {
        app: params.name,
      },
      name: params.name,
    },
  },
  {
    apiVersion: 'rbac.authorization.k8s.io/v1',
    kind: 'Role',
    metadata: {
      labels: {
        app: params.name,
      },
      name: params.name,
    },
    rules: [
      {
        apiGroups: [
          '*',
        ],
        resources: [
          '*',
        ],
        verbs: [
          '*',
        ],
      },
    ],
  },
  {
    apiVersion: 'rbac.authorization.k8s.io/v1',
    kind: 'RoleBinding',
    metadata: {
      labels: {
        app: params.name,
      },
      name: params.name,
    },
    roleRef: {
      apiGroup: 'rbac.authorization.k8s.io',
      kind: 'Role',
      name: params.name,
    },
    subjects: [
      {
        kind: 'ServiceAccount',
        name: params.name,
        namespace: env.namespace,
      },
    ],
  },
]

Nowe parametry opiszemy także w środowiska/base.libsonnet, który teraz wygląda tak:

local secrets = import '../secrets/base.libsonnet';

{
  components: {
    gitlabRunner: {
      name: 'gitlab-runner',
      values: {
        gitlabUrl: 'https://gitlab.com/',
        rbac: {
          create: true,
        },
        runnerRegistrationToken: secrets.runnerRegistrationToken,
        runners: {
          serviceAccountName: $.components.rbac.name,
          image: 'registry.gitlab.com/kvaps/docs.example.org/toolbox:v0.0.1',
        },
      },
    },
    rbac: {
      name: 'gitlab-runner-deploy',
    },
  },
}

Zauważyć $.components.rbac.name odnosi się do Nazwa dla komponentu rb

Sprawdźmy co się zmieniło:

qbec diff default

i zastosuj nasze zmiany w Kubernetes:

qbec apply default

Nie zapomnij także zatwierdzić naszych zmian w git:

cd ../..
git add dockerfiles/toolbox
git commit -m "Add Dockerfile for toolbox"
git add deploy/gitlab-runner
git commit -m "Configure gitlab-runner to use toolbox"

9. Nasz pierwszy potok i montaż obrazów według tagów

U podstaw projektu, który utworzymy .gitlab-ci.yml o następującej treści:

.build_docker_image:
  stage: build
  image:
    name: gcr.io/kaniko-project/executor:debug-v0.15.0
    entrypoint: [""]
  before_script:
    - echo "{"auths":{"$CI_REGISTRY":{"username":"$CI_REGISTRY_USER","password":"$CI_REGISTRY_PASSWORD"}}}" > /kaniko/.docker/config.json

build_toolbox:
  extends: .build_docker_image
  script:
    - /kaniko/executor --cache --context $CI_PROJECT_DIR/dockerfiles/toolbox --dockerfile $CI_PROJECT_DIR/dockerfiles/toolbox/Dockerfile --destination $CI_REGISTRY_IMAGE/toolbox:$CI_COMMIT_TAG
  only:
    refs:
      - tags

build_website:
  extends: .build_docker_image
  variables:
    GIT_SUBMODULE_STRATEGY: normal
  script:
    - /kaniko/executor --cache --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/dockerfiles/website/Dockerfile --destination $CI_REGISTRY_IMAGE/website:$CI_COMMIT_TAG
  only:
    refs:
      - tags

Pamiętaj, że używamy GIT_SUBMODULE_STRATEGY: normalne dla tych zadań, w których musisz jawnie zainicjować podmoduły przed wykonaniem.

Nie zapomnij zatwierdzić naszych zmian:

git add .gitlab-ci.yml
git commit -m "Automate docker build"

Myślę, że śmiało możemy nazwać to wersją v0.0.1 i dodaj tag:

git tag v0.0.1

Będziemy dodawać tagi za każdym razem, gdy będziemy musieli wydać nową wersję. Tagi w obrazach Dockera zostaną powiązane z tagami Git. Każde naciśnięcie nowego tagu zainicjuje tworzenie obrazów z tym tagiem.

Zróbmy to git push --tagsi spójrzmy na nasz pierwszy potok:

Zrzut ekranu pierwszego rurociągu

Wypróbowanie nowych narzędzi do budowania i automatyzacji wdrażania w Kubernetes

Warto zwrócić uwagę na fakt, że montaż za pomocą tagów nadaje się do budowania obrazów dockerowych, ale nie nadaje się do wdrażania aplikacji na Kubernetesie. Ponieważ do starych zatwierdzeń można przypisać nowe tagi, w tym przypadku zainicjowanie dla nich potoku doprowadzi do wdrożenia starej wersji.

Aby rozwiązać ten problem, zazwyczaj kompilacja obrazów dokowanych jest powiązana ze znacznikami i wdrażaniem aplikacji w gałęzi mistrz, w którym wersje zebranych obrazów są zakodowane na stałe. W tym miejscu możesz zainicjować wycofywanie za pomocą prostego przywracania mistrz-gałęzie.

10. Automatyzacja wdrożeń

Aby Gitlab-runner odszyfrował nasze sekrety, będziemy musieli wyeksportować klucz repozytorium i dodać go do naszych zmiennych środowiskowych CI:

git crypt export-key /tmp/docs-repo.key
base64 -w0 /tmp/docs-repo.key; echo

Wynikową linię zapiszemy w Gitlabie; w tym celu przejdźmy do ustawień naszego projektu:
Ustawienia -> CI / CD -> Zmienne

I utwórzmy nową zmienną:

Rodzaj Nieruchomości
Klawisz
wartość
Chroniony
Zamaskowany
Zakres

File
GITCRYPT_KEY
<your string>
true (w trakcie szkolenia możesz false)
true
All environments

Zrzut ekranu dodanej zmiennej

Wypróbowanie nowych narzędzi do budowania i automatyzacji wdrażania w Kubernetes

Teraz zaktualizujmy nasze .gitlab-ci.yml dodając do tego:

.deploy_qbec_app:
  stage: deploy
  only:
    refs:
      - master

deploy_gitlab_runner:
  extends: .deploy_qbec_app
  variables:
    GIT_SUBMODULE_STRATEGY: normal
  before_script:
    - base64 -d "$GITCRYPT_KEY" | git-crypt unlock -
  script:
    - qbec apply default --root deploy/gitlab-runner --force:k8s-context __incluster__ --wait --yes

deploy_website:
  extends: .deploy_qbec_app
  script:
    - qbec apply default --root deploy/website --force:k8s-context __incluster__ --wait --yes

Tutaj włączyliśmy kilka nowych opcji dla qbec:

  • --rootuj niektóre/aplikacje — umożliwia określenie katalogu konkretnej aplikacji
  • --force:k8s-context __incluster__ - jest to magiczna zmienna, która mówi, że wdrożenie nastąpi w tym samym klastrze, w którym działa gtilab-runner. Jest to konieczne, ponieważ w przeciwnym razie qbec będzie próbował znaleźć odpowiedni serwer Kubernetes w twoim kubeconfig
  • --Czekać — zmusza qbec do czekania, aż utworzone przez niego zasoby przejdą do stanu Gotowe i dopiero wtedy zakończy działanie z pomyślnym kodem wyjścia.
  • -Tak - po prostu wyłącza interaktywną powłokę Jesteś pewny? po rozłożeniu.

Nie zapomnij zatwierdzić naszych zmian:

git add .gitlab-ci.yml
git commit -m "Automate deploy"

Oraz po git push zobaczymy jak nasze aplikacje zostały wdrożone:

Zrzut ekranu drugiego rurociągu

Wypróbowanie nowych narzędzi do budowania i automatyzacji wdrażania w Kubernetes

11. Artefakty i montaż podczas naciskania na mistrza

Zazwyczaj opisane powyżej kroki wystarczą do zbudowania i dostarczenia niemal każdego mikroserwisu, jednak nie chcemy dodawać tagu za każdym razem, gdy musimy zaktualizować witrynę. Dlatego wybierzemy bardziej dynamiczną trasę i skonfigurujemy wdrożenie podsumowujące w gałęzi głównej.

Pomysł jest prosty: teraz wizerunek naszego zostanie odbudowany za każdym razem, gdy wciśniesz mistrz, a następnie automatycznie wdrażaj w Kubernetes.

Zaktualizujmy te dwa zadania w naszym .gitlab-ci.yml:

build_website:
  extends: .build_docker_image
  variables:
    GIT_SUBMODULE_STRATEGY: normal
  script:
    - mkdir -p $CI_PROJECT_DIR/artifacts
    - /kaniko/executor --cache --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/dockerfiles/website/Dockerfile --destination $CI_REGISTRY_IMAGE/website:$CI_COMMIT_REF_NAME --digest-file $CI_PROJECT_DIR/artifacts/website.digest
  artifacts:
    paths:
      - artifacts/
  only:
    refs:
      - master
      - tags

deploy_website:
  extends: .deploy_qbec_app
  script:
    - DIGEST="$(cat artifacts/website.digest)"
    - qbec apply default --root deploy/website --force:k8s-context __incluster__ --wait --yes --vm:ext-str digest="$DIGEST"

Uwaga, dodaliśmy wątek mistrz к ref dla pracy strona_kompilacji i teraz używamy $CI_COMMIT_REF_NAME zamiast $CI_COMMIT_TAG, czyli jesteśmy odwiązani od tagów w Git i teraz wypchniemy obraz z nazwą gałęzi zatwierdzenia, która zainicjowała potok. Warto zaznaczyć, że sprawdzi się to także w przypadku tagów, co pozwoli nam na zapisanie w rejestrze dockera snapshotów strony z określoną wersją.

Gdy nazwa tagu docker dla nowej wersji witryny może pozostać niezmieniona, pozostaje nam jeszcze opisać zmiany w Kubernetesie, inaczej po prostu nie wdroży on aplikacji z nowego obrazu, gdyż nie zauważy żadnych zmian w manifest wdrożenia.

Opcja —vm:ext-str Digest=”$DIGEST” for qbec - pozwala przekazać zmienną zewnętrzną do jsonnet. Chcemy, aby był on ponownie wdrażany w klastrze przy każdym wydaniu naszej aplikacji. Nie możemy już używać nazwy tagu, która może być teraz niezmienna, ponieważ musimy być powiązani z konkretną wersją obrazu i uruchamiać wdrożenie, gdy się ona zmieni.

Tutaj pomoże nam możliwość zapisania przez Kaniko obrazu podsumowania do pliku (opcja --plik-digest)
Następnie prześlemy ten plik i przeczytamy go w momencie wdrożenia.

Zaktualizujmy parametry naszego wdrożyć/website/environments/base.libsonnet co będzie teraz wyglądało tak:

{
  components: {
    website: {
      name: 'example-docs',
      image: 'registry.gitlab.com/kvaps/docs.example.org/website@' + std.extVar('digest'),
      replicas: 1,
      containerPort: 80,
      servicePort: 80,
      nodeSelector: {},
      tolerations: [],
      ingressClass: 'nginx',
      domain: 'docs.example.org',
    },
  },
}

Gotowe, teraz dowolne zatwierdzenie mistrz inicjuje kompilację obrazu okna dokowanego dla , a następnie wdróż go w Kubernetes.

Nie zapomnij zatwierdzić naszych zmian:

git add .
git commit -m "Configure dynamic build"

Sprawdzimy później git push powinniśmy zobaczyć coś takiego:

Zrzut ekranu przedstawiający potok dla master

Wypróbowanie nowych narzędzi do budowania i automatyzacji wdrażania w Kubernetes

W zasadzie nie musimy ponownie wdrażać gitlab-runnera przy każdym naciśnięciu, chyba że oczywiście nic się nie zmieniło w jego konfiguracji, naprawmy to .gitlab-ci.yml:

deploy_gitlab_runner:
  extends: .deploy_qbec_app
  variables:
    GIT_SUBMODULE_STRATEGY: normal
  before_script:
    - base64 -d "$GITCRYPT_KEY" | git-crypt unlock -
  script:
    - qbec apply default --root deploy/gitlab-runner --force:k8s-context __incluster__ --wait --yes
  only:
    changes:
      - deploy/gitlab-runner/**/*

zmiany pozwoli Ci monitorować zmiany w wdrożyć/gitlab-runner/ i uruchomi nasze zadanie tylko wtedy, gdy takie istnieją

Nie zapomnij zatwierdzić naszych zmian:

git add .gitlab-ci.yml
git commit -m "Reduce gitlab-runner deploy"

git push, to lepiej:

Zrzut ekranu zaktualizowanego potoku

Wypróbowanie nowych narzędzi do budowania i automatyzacji wdrażania w Kubernetes

12. Środowiska dynamiczne

Nadszedł czas, aby zdywersyfikować naszą ofertę dzięki dynamicznym środowiskom.

Najpierw zaktualizujmy zadanie strona_kompilacji w naszym .gitlab-ci.yml, usuwając z niego blok tylko, co zmusi Gitlaba do uruchomienia go przy każdym zatwierdzeniu w dowolnej gałęzi:

build_website:
  extends: .build_docker_image
  variables:
    GIT_SUBMODULE_STRATEGY: normal
  script:
    - mkdir -p $CI_PROJECT_DIR/artifacts
    - /kaniko/executor --cache --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/dockerfiles/website/Dockerfile --destination $CI_REGISTRY_IMAGE/website:$CI_COMMIT_REF_NAME --digest-file $CI_PROJECT_DIR/artifacts/website.digest
  artifacts:
    paths:
      - artifacts/

Następnie zaktualizuj zadanie wdrożyć_stronę internetową, dodaj tam blok środowisko:

deploy_website:
  extends: .deploy_qbec_app
  environment:
    name: prod
    url: https://docs.example.org
  script:
    - DIGEST="$(cat artifacts/website.digest)"
    - qbec apply default --root deploy/website --force:k8s-context __incluster__ --wait --yes --vm:ext-str digest="$DIGEST"

Umożliwi to Gitlabowi powiązanie zadania z szturchać środowisku i wyświetlić poprawny link do niego.

Dodajmy teraz jeszcze dwa zadania:

deploy_website:
  extends: .deploy_qbec_app
  environment:
    name: prod
    url: https://docs.example.org
  script:
    - DIGEST="$(cat artifacts/website.digest)"
    - qbec apply default --root deploy/website --force:k8s-context __incluster__ --wait --yes --vm:ext-str digest="$DIGEST"

deploy_review:
  extends: .deploy_qbec_app
  environment:
    name: review/$CI_COMMIT_REF_NAME
    url: http://$CI_ENVIRONMENT_SLUG.docs.example.org
    on_stop: stop_review
  script:
    - DIGEST="$(cat artifacts/website.digest)"
    - qbec apply review --root deploy/website --force:k8s-context __incluster__ --wait --yes --vm:ext-str digest="$DIGEST" --vm:ext-str subdomain="$CI_ENVIRONMENT_SLUG" --app-tag "$CI_ENVIRONMENT_SLUG"
  only:
    refs:
    - branches
  except:
    refs:
      - master

stop_review:
  extends: .deploy_qbec_app
  environment:
    name: review/$CI_COMMIT_REF_NAME
    action: stop
  stage: deploy
  before_script:
    - git clone "$CI_REPOSITORY_URL" master
    - cd master
  script:
    - qbec delete review --root deploy/website --force:k8s-context __incluster__ --yes --vm:ext-str digest="$DIGEST" --vm:ext-str subdomain="$CI_ENVIRONMENT_SLUG" --app-tag "$CI_ENVIRONMENT_SLUG"
  variables:
    GIT_STRATEGY: none
  only:
    refs:
    - branches
  except:
    refs:
      - master
  when: manual

Zostaną uruchomione po wypchnięciu do dowolnej gałęzi z wyjątkiem głównej i wdrożą wersję poglądową witryny.

Widzimy nową opcję dla qbec: --tag-aplikacji — umożliwia tagowanie wdrożonych wersji aplikacji i pracę wyłącznie w obrębie tego tagu; podczas tworzenia i niszczenia zasobów w Kubernetesie qbec będzie działał tylko z nimi.
W ten sposób nie możemy stworzyć osobnego środowiska dla każdej recenzji, ale po prostu ponownie wykorzystać to samo.

Tutaj również używamy recenzja zastosowania qbec, zamiast qbec zastosuj domyślnie - to jest dokładnie ten moment, w którym spróbujemy opisać różnice pomiędzy naszymi środowiskami (przeglądowymi i domyślnymi):

Dodajmy przeglądu środowisko w wdrożyć/website/qbec.yaml

spec:
  environments:
    review:
      defaultNamespace: docs
      server: https://kubernetes.example.org:8443

Wtedy to ogłosimy wdrożyć/website/params.libsonnet:

local env = std.extVar('qbec.io/env');
local paramsMap = {
  _: import './environments/base.libsonnet',
  default: import './environments/default.libsonnet',
  review: import './environments/review.libsonnet',
};

if std.objectHas(paramsMap, env) then paramsMap[env] else error 'environment ' + env + ' not defined in ' + std.thisFile

I zapisz dla niego niestandardowe parametry w wdrożyć/website/environments/review.libsonnet:

// this file has the param overrides for the default environment
local base = import './base.libsonnet';
local slug = std.extVar('qbec.io/tag');
local subdomain = std.extVar('subdomain');

base {
  components+: {
    website+: {
      name: 'example-docs-' + slug,
      domain: subdomain + '.docs.example.org',
    },
  },
}

Przyjrzyjmy się także bliżej Jobu stop_przegląd, zostanie wywołany, gdy gałąź zostanie usunięta i aby gitlab nie próbował sprawdzić, jest używany GIT_STRATEGY: brak, później klonujemy mistrz-odgałęzij i usuń przez niego recenzję.
To trochę skomplikowane, ale nie znalazłem jeszcze piękniejszego sposobu.
Alternatywną opcją byłoby umieszczenie każdej recenzji w przestrzeni nazw hotelu, którą zawsze można całkowicie usunąć.

Nie zapomnij zatwierdzić naszych zmian:

git add .
git commit -m "Enable automatic review"

git push, git checkout -b test, git push test pochodzenia, sprawdzać:

Zrzut ekranu utworzonych środowisk w Gitlabie

Wypróbowanie nowych narzędzi do budowania i automatyzacji wdrażania w Kubernetes

Wszystko działa? - świetnie, usuń naszą gałąź testową: git checkout master, git push pochodzenia: test, sprawdzamy, czy zadania usuwania środowiska działały bez błędów.

Tutaj chciałbym od razu wyjaśnić, że każdy programista w projekcie może tworzyć gałęzie, może też zmieniać .gitlab-ci.yml plik i uzyskaj dostęp do tajnych zmiennych.
Dlatego zdecydowanie zaleca się zezwolenie na ich stosowanie wyłącznie w przypadku gałęzi chronionych, np mistrzlub utwórz oddzielny zestaw zmiennych dla każdego środowiska.

13. Przejrzyj aplikacje

Przejrzyj aplikacje Jest to funkcja GitLab, która pozwala dodać przycisk do każdego pliku w repozytorium, aby szybko wyświetlić go we wdrożonym środowisku.

Aby te przyciski się pojawiły, musisz utworzyć plik .gitlab/route-map.yml i opisz w nim wszystkie transformacje ścieżki; w naszym przypadku będzie to bardzo proste:

# Indices
- source: /content/(.+?)_index.(md|html)/ 
  public: '1'

# Pages
- source: /content/(.+?).(md|html)/ 
  public: '1/'

Nie zapomnij zatwierdzić naszych zmian:

git add .gitlab/
git commit -m "Enable review apps"

git push, i zaznacz:

Zrzut ekranu przedstawiający przycisk Przejrzyj aplikację

Wypróbowanie nowych narzędzi do budowania i automatyzacji wdrażania w Kubernetes

Praca jest wykonywana!

Źródła projektu:

Dziękuję za uwagę, mam nadzieję, że Ci się podobało Wypróbowanie nowych narzędzi do budowania i automatyzacji wdrażania w Kubernetes

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

Dodaj komentarz