Fermer

février 26, 2024

Création de modèles de pipeline réutilisables dans les flux de travail d’actions GitHub / Blogs / Perficient

Création de modèles de pipeline réutilisables dans les flux de travail d’actions GitHub / Blogs / Perficient


Introduction aux modèles de pipeline

Dans le paysage agile actuel du développement logiciel, les équipes s’appuient largement sur des flux de travail robustes appelés « pipelines » pour automatiser les tâches et améliorer la productivité. Pour les équipes DevOps qui connaissaient historiquement la plateforme Azure DevOps CICD Automation de Microsoft, l’une des fonctionnalités les plus puissantes déployées par la plateforme qui a permis aux équipes d’accélérer considérablement le processus de développement du pipeline était : «Modèles YAML« .

Les modèles YAML dans Azure DevOps sont des fichiers de configuration réutilisables écrits en YAML, nous permettant d’appliquer les meilleures pratiques, d’établir des versions et d’accélérer l’automatisation de la création et de la publication pour de grands groupes d’équipes. Ces modèles facilitent une intégration plus rapide, une maintenance simplifiée grâce à des mises à jour et un contrôle de version centralisés, ainsi que l’application de mesures de sécurité et de normes de conformité intégrées. En fin de compte, ces modèles favorisent la collaboration, l’amélioration continue et les processus de déploiement d’infrastructure rationalisés pour les grandes organisations qui disposent de nombreuses équipes avec des cas d’utilisation d’automatisation similaires.

Cependant, pour beaucoup de mes clients qui ne construisent pas dans l’écosystème Azure, une question revient fréquemment : comment pouvons-nous réaliser cette même fonctionnalité de modèle dans d’autres chaînes d’outils ? Une plate-forme qui gagne rapidement en popularité dans le domaine de l’automatisation DevOps est Actions GitHub. GitHub Actions se distingue par une intégration transparente dans l’écosystème GitHub, fournissant une solution CI/CD intuitive via des configurations YAML dans les référentiels. Sa force réside dans une approche conviviale, tirant parti d’un marché riche pour les actions prédéfinies et de multiples fonctionnalités de sécurité de code intégrées.

Dans le blog d’aujourd’hui, je vais expliquer comment nous pouvons implémenter la même fonctionnalité de création de modèles à l’aide des actions GitHub. afin que nous puissions partager du code commun, les meilleures pratiques et la sécurité appliquée entre plusieurs équipes afin de fournir une approche structurée et versionnable pour définir les configurations de pipeline, favorisant la réutilisabilité, la cohérence et les pratiques de développement collaboratif pour l’automatisation de la construction et de la publication sur n’importe quelle pile.


Configuration de GitHub

GitHub offre un environnement structuré de collaboration au sein d’une entreprise. La structure de niveau supérieur de GitHub est un « Compte d’entreprise ». Cependant, à l’intérieur d’un compte, une entreprise peut créer plusieurs Organisations: Les organisations sont une construction de groupe que les entreprises peuvent utiliser pour organiser les utilisateurs et les équipes afin qu’ils puissent collaborer sur plusieurs projets à la fois. Les organisations offrent des fonctionnalités de sécurité et d’administration sophistiquées qui permettent aux entreprises de gérer leurs équipes et leurs unités commerciales. En tirant parti de cette structure, les administrateurs peuvent gérer les contrôles d’accès aux référentiels, notamment en régulant l’accès aux workflows Starter. En configurant les paramètres du référentiel et les autorisations d’accès, les entreprises peuvent garantir que ces flux de travail prédéfinis ne sont accessibles qu’aux membres de leur organisation.

En règle générale, les entreprises disposent d’une seule unité commerciale ou d’un seul département responsable de leurs environnements cloud (que cette équipe crée réellement tous les environnements cloud ou soit simplement en charge des fondations cloud plus larges, la sécurité et la gouvernance varient en fonction de la taille et de la complexité de l’environnement cloud. entreprise et leurs besoins en infrastructure). La création d’une organisation unique pour cette unité commerciale est logique car elle permet à toutes les équipes de cette unité de partager du code et de contrôler les meilleures pratiques cloud à partir d’un emplacement unique auquel d’autres unités commerciales ou organisations peuvent référencer.

Pour simuler cela je vais mettre en place une nouvelle organisation pour un département « Cloud OPs » :

configurationorg

Une fois mon organisation configurée, je peux personnaliser les politiques, les paramètres de sécurité ou d’autres paramètres pour protéger les ressources de mon service. Je ne vais pas entrer dans les détails, mais vous trouverez ci-dessous quelques bons articles de la documentation GitHub qui passent en revue certains paramètres, rôles et autres fonctionnalités courants qui doivent être configurés par votre entreprise lorsque vous avez créé une nouvelle organisation GitHub.

https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/roles-in-an-organization

https://docs.github.com/en/organizations/managing-user-access-to-your-organizations-repositories

L’étape suivante après la création de l’organisation consiste à créer un référentiel pour héberger nos modèles :

Initialiser le dépôt Github

Maintenant que nous avons un référentiel, nous sommes prêts à commencer à écrire notre code YAML !

Comparons les modèles Azure DevOps aux flux de travail GitHub Actions Starter :

Une fois la configuration de GitHub terminée, plongeons-nous et commençons à convertir un modèle Azure DevOps en un flux de travail Github Starter afin que nous puissions le stocker dans notre dépôt.
En tant que directeur technique dans une équipe de conseil cloud, l’un des cas d’utilisation les plus courants que j’ai pour utiliser des modèles de pipeline est le partage de données communes. Automatisation Terraform.
En utilisant des modèles Azure DevOps Pipeline pour regrouper des étapes d’automatisation Terraform communes et reproductibles, je suis en mesure de fournir une approche standardisée, efficace et flexible pour créer des pipelines de déploiement d’infrastructure réutilisables dans toutes les équipes de mon entreprise.

Vous trouverez ci-dessous un exemple de quelques modèles Terraform courants utilisés par mon équipe :

Modèles Ado de démarrage

Toute équipe automatisant l’infrastructure avec Terraform va écrire une automatisation pour exécuter ces trois processus. Il est donc logique de les écrire une fois sous forme de modèle afin que les futures équipes puissent étendre leurs pipelines à partir de ces modèles de base pour accélérer leur processus de développement. Voici quelques versions très basiques de ce à quoi peuvent ressembler ces modèles écrits pour les pipelines Azure DevOps :

###VALIDATE TEMPLATE###

parameters:
  - name: terraformPath
    type: string

stages:
  - stage: validate
    displayName: "Terraform validate"
    jobs:
      - job: validate
        displayName: "Terraform validate"

        variables:
          - name: terraformWorkingDirectory
            value: $(System.DefaultWorkingDirectory)/${{ parameters.terraformPath }}

        steps:
          - checkout: self

          - task: PowerShell@2
            displayName: "Terraform init"
            inputs:
              targetType: 'inline'
              pwsh: true
              workingDirectory: $(terraformWorkingDirectory)
              script: |
                terraform init -backend=false

          - task: PowerShell@2
            displayName: "Terraform fmt"
            inputs:
              targetType: 'inline'
              pwsh: true
              workingDirectory: $(terraformWorkingDirectory)
              script: |
                terraform fmt -check -write=false -recursive

          - task: PowerShell@2
            displayName: "Terraform validate"
            inputs:
              targetType: 'inline'
              pwsh: true
              workingDirectory: $(terraformWorkingDirectory)
              script: |
                terraform validate       
###PLAN TEMPLATE###

parameters:
  - name: condition
    type: string
  - name: dependsOnStage
    type: string
  - name: environment
    type: string
  - name: serviceConnection
    type: string
  - name: terraformPath
    type: string
  - name: terraformVarFile
    type: string

stages:
  - stage: plan
    displayName: "Terraform plan: ${{ parameters.environment }}"
    condition: and(succeeded(), ${{ parameters.condition }})
    dependsOn: ${{ parameters.dependsOnStage }}
    jobs:
      - job: plan
        displayName: "Terraform plan"

        steps:

          - checkout: self

          - task: AzureCLI@2
            displayName: "Terraform init"
            inputs:
              scriptType: bash
              scriptLocation: inlineScript
              azureSubscription: "${{ parameters.serviceConnection }}"
              addSpnToEnvironment: true
              workingDirectory: ${{ parameters.terraformPath }}
              inlineScript: |
                export ARM_CLIENT_ID=$servicePrincipalId
                export ARM_CLIENT_SECRET=$servicePrincipalKey                
                export ARM_SUBSCRIPTION_ID=$DEPLOYMENT_SUBSCRIPTION_ID
                export ARM_TENANT_ID=$tenantId

                terraform init \
                  -backend-config="subscription_id=$TFSTATE_SUBSCRIPTION_ID" \
                  -backend-config="resource_group_name=$TFSTATE_RESOURCE_GROUP_NAME" \
                  -backend-config="storage_account_name=$TFSTATE_STORAGE_ACCOUNT_NAME" \
                  -backend-config="container_name=$TFSTATE_CONTAINER_NAME" \
                  -backend-config="key=$TFSTATE_KEY"                  
            env:
              DEPLOYMENT_SUBSCRIPTION_ID: $(${{ parameters.environment }}DeploymentSubscriptionID)
              TFSTATE_CONTAINER_NAME: $(TfstateContainerName)
              TFSTATE_KEY: $(TfstateKey)
              TFSTATE_RESOURCE_GROUP_NAME: $(TfstateResourceGroupName)
              TFSTATE_STORAGE_ACCOUNT_NAME: $(TfstateStorageAccountName)
              TFSTATE_SUBSCRIPTION_ID: $(TfstateSubscriptionID)

          - task: AzureCLI@2
            displayName: "Terraform plan"
            inputs:
              scriptType: bash
              scriptLocation: inlineScript
              azureSubscription: "${{ parameters.serviceConnection }}"
              addSpnToEnvironment: true
              workingDirectory: ${{ parameters.terraformPath }}
              inlineScript: |
                export ARM_CLIENT_ID=$servicePrincipalId
                export ARM_CLIENT_SECRET=$servicePrincipalKey                
                export ARM_SUBSCRIPTION_ID=$DEPLOYMENT_SUBSCRIPTION_ID
                export ARM_TENANT_ID=$tenantId

                terraform workspace select $(${{ parameters.environment }}TfWorkspaceName)

                terraform plan -var-file '${{ parameters.terraformVarFile }}'
            env:
              DEPLOYMENT_SUBSCRIPTION_ID: $(${{ parameters.environment }}DeploymentSubscriptionID)
###APPLY TEMPLATE###


parameters:
  - name: condition
    type: string
  - name: dependsOnStage
    type: string
  - name: environment
    type: string
  - name: serviceConnection
    type: string
  - name: terraformPath
    type: string
  - name: terraformVarFile
    type: string

stages:
  - stage: apply
    displayName: "Terraform apply: ${{ parameters.environment }}"
    condition: and(succeeded(), ${{ parameters.condition }})
    dependsOn: ${{ parameters.dependsOnStage }}
    jobs:
      - deployment: apply
        displayName: "Terraform apply"
        environment: "fusion-terraform-${{ parameters.environment }}"
        strategy:
          runOnce:
            deploy:
              steps:
      
                - checkout: self
      
                - task: AzureCLI@2
                  displayName: "Terraform init"
                  inputs:
                    scriptType: bash
                    scriptLocation: inlineScript
                    azureSubscription: "${{ parameters.serviceConnection }}"
                    addSpnToEnvironment: true
                    workingDirectory: ${{ parameters.terraformPath }}
                    inlineScript: |
                      export ARM_CLIENT_ID=$servicePrincipalId
                      export ARM_CLIENT_SECRET=$servicePrincipalKey                
                      export ARM_SUBSCRIPTION_ID=$DEPLOYMENT_SUBSCRIPTION_ID
                      export ARM_TENANT_ID=$tenantId

                      terraform init \
                        -backend-config="subscription_id=$TFSTATE_SUBSCRIPTION_ID" \
                        -backend-config="resource_group_name=$TFSTATE_RESOURCE_GROUP_NAME" \
                        -backend-config="storage_account_name=$TFSTATE_STORAGE_ACCOUNT_NAME" \
                        -backend-config="container_name=$TFSTATE_CONTAINER_NAME" \
                        -backend-config="key=$TFSTATE_KEY"                  
                  env:
                    DEPLOYMENT_SUBSCRIPTION_ID: $(${{ parameters.environment }}DeploymentSubscriptionID)
                    TFSTATE_CONTAINER_NAME: $(TfstateContainerName)
                    TFSTATE_KEY: $(TfstateKey)
                    TFSTATE_RESOURCE_GROUP_NAME: $(TfstateResourceGroupName)
                    TFSTATE_STORAGE_ACCOUNT_NAME: $(TfstateStorageAccountName)
                    TFSTATE_SUBSCRIPTION_ID: $(TfstateSubscriptionID)
      
                - task: AzureCLI@2
                  displayName: "Terraform apply"
                  inputs:
                    scriptType: pscore
                    scriptLocation: inlineScript
                    azureSubscription: "${{ parameters.serviceConnection }}"
                    addSpnToEnvironment: true
                    workingDirectory: ${{ parameters.terraformPath }}
                    inlineScript: |
                      $env:ARM_CLIENT_ID=$env:servicePrincipalId
                      $env:ARM_CLIENT_SECRET=$env:servicePrincipalKey                
                      $env:ARM_SUBSCRIPTION_ID=$env:DEPLOYMENT_SUBSCRIPTION_ID
                      $env:ARM_TENANT_ID=$env:tenantId
      
                      terraform workspace select $(${{ parameters.environment }}TfWorkspaceName)
                    
                      terraform apply -var-file '${{ parameters.terraformVarFile }}' -auto-approve
                  env:
                    DEPLOYMENT_SUBSCRIPTION_ID: $(${{ parameters.environment }}DeploymentSubscriptionID)

Les trois étapes de l’automatisation de Terraform sont regroupées en ensembles d’étapes répétables. ALORS, lorsque d’autres équipes souhaitent utiliser ces modèles dans leurs propres pipelines, elles peuvent simplement les appeler en utilisant un lien « Ressources » dans leur code :

Exemple de modèles Yaml

Comment pouvons-nous appliquer ce même concept dans GitHub Actions ?

Github Actions propose le concept de « Flux de travail de démarrage » comme leur version des « Modèles de pipeline » proposés par Azure DevOps. Les workflows de démarrage sont des modèles préconfigurés conçus pour accélérer la configuration de workflows automatisés au sein des référentiels, fournissant une base avec des configurations par défaut et des actions prédéfinies pour rationaliser la création de pipelines CI/CD dans GitHub Actions.

La bonne nouvelle est qu’Azure DevOps YAML et GitHub Actions YAML utilisent une syntaxe très similaire – de sorte que le processus de conversion d’un modèle de pipeline Azure DevOps en un workflow de démarrage ne représente pas un effort majeur. Certaines fonctionnalités ne sont pas encore prises en charge dans les actions GitHub, je dois donc en tenir compte lorsque je démarre ma conversion. Consultez ce document pour connaître les différences entre les deux plates-formes du point de vue des fonctionnalités :

https://learn.microsoft.com/en-us/dotnet/architecture/devops-for-aspnet-developers/actions-vs-pipelines

Pour démontrer le concept, je vais convertir le modèle « Valider » que j’ai montré ci-dessus en un workflow GitHub Actions Starter et nous pouvons le comparer à la version originale d’Azure DevOps :

Comparaison

Voici une liste de ce que nous avons dû changer :

  1. Contributions: Ce qu’Azure DevOps appelle « Paramètres », appelle GitHub Actions « Contributions ». Les entrées nous permettent de transmettre des paramètres, des variables ou des indicateurs de fonctionnalités dans le modèle afin que nous puissions contrôler le comportement.
  2. Étapes: Azure DevOps propose une fonctionnalité appelée « Étapes » qui sont des limites logiques dans un pipeline. Nous utilisons les étapes pour marquer la séparation des préoccupations (par exemple, construction, assurance qualité et production). Chaque étape agit en tant que parent pour un ou plusieurs Jobs. GitHub, cependant, ne prend pas en charge les étapes. DONC, nous devons supprimer la syntaxe de la scène et utiliser une logique déclarative et des commentaires efficaces dans nos scripts pour diviser logiquement le flux de travail.
  3. Variables : Azure DevOps offre la possibilité de définir diverses paires clé-valeur de chaîne que vous pourrez ensuite utiliser ultérieurement dans votre pipeline. Comme vous pouvez le voir dans nos modèles ci-dessus, nous utilisons des variables pour définir le chemin du fichier où se trouve notre fichier main.tf dans notre référentiel de code Terraform. Comme ces informations sont susceptibles d’être nécessaires à plusieurs étapes différentes de l’automatisation, il est logique de définir la valeur une seule fois plutôt que de la coder en dur partout où elle doit être utilisée. Les actions GitHub utilisent une syntaxe différente pour définir les variables : https://docs.github.com/en/actions/learn-github-actions/variables#defining-environment-variables-for-a-single-workflow.
    Cependant, le concept est très similaire dans son exécution à la façon dont Azure DevOps définit ses variables. Comme on peut le voir dans la comparaison ci-dessus, nous spécifions que nous créons des variables d’environnement, puis nous transmettons la paire clé-valeur à créer.
  4. Type de coque : Azure DevOps propose plusieurs types de tâches prédéfinis. Celles-ci sont analogues aux actions prédéfinies de Github. Mais dans Azure DevOps, le type de shell est généralement spécifié par le type de tâche que vous sélectionnez. Dans GitHub Actions, cependant, nous devons spécifier le type de shell que nous devons cibler pour ce workflow. Il existe différents types de coques parmi lesquelles choisir :
    plates-formesCoquilleDescription
    Tous (windows + Linux)pythonExécute la commande python
    Tous (windows + Linux)pwshShell par défaut utilisé sous Windows, doit être spécifié sur d’autres coureur types d’environnement
    Tous (windows + Linux)bashLe shell par défaut utilisé sur les plates-formes non Windows doit être spécifié sur d’autres coureur types d’environnement
    Linux/MacOSshLe comportement de secours pour les plates-formes non Windows si aucun shell n’est fourni et que bash n’est pas trouvé dans le chemin.
    les fenêtrescmdGitHub ajoute l’extension .cmd à votre script
    les fenêtresPowerShellLe bureau PowerShell. GitHub ajoute l’extension .ps1 au nom de votre script.

    lorsque nous transmettons le paramètre shell, nous indiquons au coureur qui exécutera nos scripts YAML, quel outil de ligne de commande il doit utiliser pour garantir que nous ne rencontrons aucun comportement inattendu.

  5. Secrets : Ce dernier point est moins évident que les autres car les secrets d’un pipeline ne sont pas réellement inclus dans le code YAML lui-même (mettre des secrets dans votre code source est un anti-modèle que vous ne devriez jamais faire pour éviter les fuites d’informations d’identification). Dans Azure DevOps, il existe plusieurs options pour fournir des secrets ou des informations d’identification à votre pipeline, notamment Connexions de services et Groupes de bibliothèques.
    Nous ajoutons les secrets dans le groupe de bibliothèques, puis choisissons l’option permettant de définir la variable comme secret :

Secrets

Cela chiffrera la variable avec un cryptage unidirectionnel afin qu’elle ne soit jamais visible. Ces groupes de bibliothèques peuvent ensuite être ajoutés à votre pipeline en tant que variables d’environnement à l’aide de la syntaxe des variables ADO :

Référence des variables

Dans GitHub, le portail propose une option de secrets similaire : https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#about-secrets. Accédez au menu des paramètres du dépôt où résidera votre pipeline de niveau supérieur (REMARQUE – il s’agit généralement du « Consommateur » des modèles de pipeline et non du dépôt où vivent les modèles) et sélectionnez le menu des paramètres. À partir de là, vous devriez voir un sous-menu dans lequel les secrets peuvent être définis !

Les secrets de GitHub

Il existe plusieurs types de secrets parmi lesquels choisir. En règle générale, les secrets d’environnement sont les plus faciles à utiliser, car ces secrets seront injectés dans votre runtime Runner en tant que variables d’environnement que vous pouvez référencer à partir de votre code YAML comme suit :

steps:
  - shell: bash
    env:
      SUPER_SECRET: ${{ secrets.SuperSecret }}
    run: |
      example-command "$SUPER_SECRET"

Avec ce changement, notre conversion est en grande partie terminée ! Jetons un coup d’œil au résultat final ci-dessous.

name: terraform-plan-base-template

# The GitHub Actions version of paramters are called "inputs"
inputs:
  terraformPath: #This is the name of the input paramter - other pipelines planning to 
                 #use this template must pass in a value for this parameter as an input
    description: "The path to the terraform directory where all our first Terraform code file (usually main.tf) will be stored."
    required: true
    type: string #GitHub Actions support the following types:  
                 #string, choice, boolean, and environment


  jobs:
    Terraform_Validate: #This is the name of the job - this will show up in the UI of GitHub 
                        #when we instantiate a workflow using this template
      runs-on: ubuntu-latest #We have to specify the runtime for the actions workflow to ensure
                             #that the scripts we plan to use below will work as expected.
      
      # GitHub Actions uses "Environment Variables" instead of "Variables" like Azure DevOps.  
      #The syntax is slightly different but the concept is the same...
      env: 
        terraformWorkingDirectory: ${{GITHUB_WORKSPACE}}/${{ inputs.terraformPath }} #In this 
        #line we are combining a default environment variable with one of our input parameters
        #The list of GitHub defined default ENV variables available is located in this doc: 
        #https://docs.github.com/en/github-ae@latest/actions/learn-github-actions/variables#default-environment-variables

      steps:
        - uses: actions/checkout@v4 #This is a pre-built action provided by github.  
                                    #Source is located here: https://github.com/actions/checkout

        - name: "Terraform init"
          shell: pwsh
          working-directory: $terraformWorkingDirectory
          run: |
            terraform init -backend=false

        - name: "Terraform fmt"
          shell: pwsh
          working-directory: $terraformWorkingDirectory
          run: |
            terraform fmt -check -write=false -recursive

        - name: "Terraform validate"
          shell: pwsh
          working-directory: $terraformWorkingDirectory
          run: |
            terraform validate

Comment utiliser les workflows de démarrage dans d’autres pipelines ?

L’étape suivante consiste à commencer à partager ces workflows de démarrage avec d’autres équipes et pipelines. Dans mon prochain blog, je montrerai comment nous pouvons partager les modèles que différentes organisations pourront utiliser !

Restez à l’écoute et continuez à « Dev-Ops-ing » !






Source link