<p>I've split out the initial <code>azure-pipelines.yml</code> to use templates, iteration, etc... For whatever reason, the new images are not being deployed despite using <code>latest</code> tag and/or <code>imagePullPolicy: Always</code>.</p>
<p>Also, I basically have two pipelines <code>PR</code> and <code>Release</code>:</p>
<ul>
<li><code>PR</code> is triggered when a PR request is submitted to merge to <code>production</code>. It automatically triggers this pipeline to run unit tests, build the Docker image, do integration tests, etc. and then pushes the image to ACR if everything passed.</li>
<li>When the <code>PR</code> pipeline is passing, and the PR is approved, it is merged into <code>production</code> which then triggers the <code>Release</code> pipeline.</li>
</ul>
<p>Here is an example of one of my <code>k8s</code> deployment manifests (the pipeline says <code>unchanged</code> when these are applied):</p>
<pre><code>apiVersion: apps/v1
kind: Deployment
metadata:
  name: admin-v2-deployment-prod
  namespace: prod
spec:
  replicas: 3
  selector:
    matchLabels:
      component: admin-v2
  template:
    metadata:
      labels:
        component: admin-v2
    spec:
      containers:
        - name: admin-v2
          imagePullPolicy: Always
          image: appacr.azurecr.io/app-admin-v2:latest
          ports:
            - containerPort: 4001
---
apiVersion: v1
kind: Service
metadata:
  name: admin-v2-cluster-ip-service-prod
  namespace: prod
spec:
  type: ClusterIP
  selector:
    component: admin-v2
  ports:
    - port: 4001
      targetPort: 4001
</code></pre>
<p>And here are the various pipeline related <code>.yamls</code> I've been splitting out:</p>
<h3>Both PR and Release:</h3>
<pre><code># templates/variables.yaml
variables:
  dockerRegistryServiceConnection: '&lt;GUID&gt;'
  imageRepository: 'app'
  containerRegistry: 'appacr.azurecr.io'
  dockerfilePath: '$(Build.SourcesDirectory)'
  tag: '$(Build.BuildId)'
  imagePullSecret: 'appacr1c5a-auth'

  vmImageName: 'ubuntu-latest'
</code></pre>
<hr />
<h3>PR:</h3>
<pre><code># pr.yaml
trigger: none

resources:
- repo: self

pool:
  vmIMage: $(vmImageName)

variables:
- template: templates/variables.yaml

stages:
- template: templates/changed.yaml
- template: templates/unitTests.yaml
- template: templates/build.yaml
  parameters: 
    services:
    - api
    - admin
    - admin-v2
    - client
- template: templates/integrationTests.yaml
</code></pre>
<pre><code># templates/build.yaml
parameters:
- name: services
  type: object
  default: []

stages:
- stage: Build
  displayName: Build stage
  jobs:
  - job: Build
    displayName: Build
    steps:
    - ${{ each service in parameters.services }}:
      - task: Docker@2
        displayName: Build and push an ${{ service }} image to container registry
        inputs:
          command: buildAndPush
          repository: $(imageRepository)-${{ service }}
          dockerfile: $(dockerfilePath)/${{ service }}/Dockerfile
          containerRegistry: $(dockerRegistryServiceConnection)
          tags: |
            $(tag)
</code></pre>
<hr />
<h3>Release:</h3>
<pre><code># release.yaml
trigger: 
  branches: 
    include:
    - production

resources:
- repo: self

variables:
- template: templates/variables.yaml

stages:
- template: templates/publish.yaml
- template: templates/deploy.yaml
  parameters: 
    services:
    - api
    - admin
    - admin-v2
    - client
</code></pre>
<pre><code># templates/deploy.yaml
parameters:
- name: services
  type: object
  default: []

stages:
- stage: Deploy
  displayName: Deploy stage
  dependsOn: Publish
  jobs:
  - deployment: Deploy
    displayName: Deploy
    pool:
      vmImage: $(vmImageName)
    environment: 'App Production AKS'
    strategy:
      runOnce:
        deploy:
          steps:
          - task: KubernetesManifest@0
            displayName: Create imagePullSecret
            inputs:
              action: createSecret
              secretName: $(imagePullSecret)
              kubernetesServiceConnection: 'App Production AKS'
              dockerRegistryEndpoint: $(dockerRegistryServiceConnection)
          - ${{ each service in parameters.services }}:
            - task: KubernetesManifest@0
              displayName: Deploy to ${{ service }} Kubernetes cluster
              inputs:
                action: deploy
                kubernetesServiceConnection: 'App Production AKS'
                manifests: |
                  $(Pipeline.Workspace)/k8s/aks/${{ service }}.yaml
                imagePullSecrets: |
                  $(imagePullSecret)
                containers: |
                  $(containerRegistry)/$(imageRepository)-${{ service }}:$(tag)
</code></pre>
<ul>
<li>Both <code>PR</code> and <code>Release</code> pass...</li>
<li>The new images are in ACR...</li>
<li>I've pulled the images to verify they have the latest changes...</li>
<li>They just aren't getting deployed to AKS.</li>
</ul>
<p>Any suggestions for what I am doing wrong here?</p>

<blockquote>
<p>For whatever reason, the new images are not being deployed despite using latest tag</p>
</blockquote>
<p>How should Kubernetes know that there is a new image? Kubernetes config is <em>declarative</em>. Kubernetes is already running what once was &quot;latest&quot; image.</p>
<blockquote>
<p>Here is an example of one of my k8s deployment manifests (the pipeline says unchanged when these are applied)</p>
</blockquote>
<p>Yeah, it is <em>unchanged</em> because the <em>declarative disered state</em> has <strong>not</strong> changed. The Deployment-manifest states <em>what</em> should be deployed, it is not a command.</p>
<h2>Proposed solution</h2>
<p>Whenever you build an image, always give it a unique name. And whenever you want to deploy something, always set a unique name of <em>what</em> should be running - then Kubernetes will manage this in an elegant zero-downtime way using rolling deployments - unless you configure it to behave different.</p>
