TAP on GKE (1.0.0) : Part 3 – Deploy an application with testing and scanning in gcr repo

Reading Time: 5 mins

Overview

In this section, I will walk you through the steps required to deploy an application using the Tanzu Application Platform. Before moving further, please ensure below are completed:

  • Prepare set up is completed, If not done, then follow the steps in the post
  • Default kubeconfig context is set to the target Kubernetes cluster.
  • Tanzu Application Platform GUI is successfully installed, for more details, read here

Install Supply Chain with Testing and Scanning

This Cartographer Supply Chain ties a series of Kubernetes resources which, when working together, drives a developer-provided Workload from source code all the way to a Kubernetes configuration ready to be deployed to a cluster, having not only passed that source code through testing and vulnerability scanning, but also the container image produced. It includes below capabilities:

  • Watching a Git Repository or local directory for changes
  • Running tests from a developer-provided Tekton or Pipeline
  • Scanning the source code for known vulnerabilities using Grype
  • Building a container image out of the source code with Buildpacks
  • Scanning the image for known vulnerabilities
  • Applying operator-defined conventions to the container definition
  • Deploying the application to the same cluster

You can verify that you have the right set of supply chains installed by running the following command:

$  tanzu apps cluster-supply-chain list
NAME                      READY   AGE    LABEL SELECTOR
source-test-scan-to-url   Ready   123m   apps.tanzu.vmware.com/has-tests=true,apps.tanzu.vmware.com/workload-type=web

Setup Developer Namespaces to use Installed Packages

To create workload for your application using the registry credentials specified, run these commands to add credentials and Role-Based Access Control (RBAC) rules to the namespace that you plan to create the workload in:

# Syntax: 

kubectl create secret docker-registry registry-credentials --docker-server=REGISTRY-SERVER --docker-username=REGISTRY-USERNAME --docker-password=REGISTRY-PASSWORD -n YOUR-NAMESPACE

# Example:  where <key>.json is the key file downloaded from GCP portal. 

kubectl create secret docker-registry registry-credentials --docker-server=gcr.io --docker-username=_json_key --docker-password="$(cat <key>.json)" -n tap-install
  • Add placeholder read secrets, a service account, and RBAC rules to the developer namespace by running:
cat <<EOF | kubectl -n tap-install apply -f -

apiVersion: v1
kind: Secret
metadata:
  name: tap-registry
  annotations:
    secretgen.carvel.dev/image-pull-secret: ""
type: kubernetes.io/dockerconfigjson
data:
  .dockerconfigjson: e30K

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: default
secrets:
  - name: registry-credentials
imagePullSecrets:
  - name: registry-credentials
  - name: tap-registry

---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: default
rules:
- apiGroups: [source.toolkit.fluxcd.io]
  resources: [gitrepositories]
  verbs: ['*']
- apiGroups: [source.apps.tanzu.vmware.com]
  resources: [imagerepositories]
  verbs: ['*']
- apiGroups: [carto.run]
  resources: [deliverables, runnables]
  verbs: ['*']
- apiGroups: [kpack.io]
  resources: [images]
  verbs: ['*']
- apiGroups: [conventions.apps.tanzu.vmware.com]
  resources: [podintents]
  verbs: ['*']
- apiGroups: [""]
  resources: ['configmaps']
  verbs: ['*']
- apiGroups: [""]
  resources: ['pods']
  verbs: ['list']
- apiGroups: [tekton.dev]
  resources: [taskruns, pipelineruns]
  verbs: ['*']
- apiGroups: [tekton.dev]
  resources: [pipelines]
  verbs: ['list']
- apiGroups: [kappctrl.k14s.io]
  resources: [apps]
  verbs: ['*']
- apiGroups: [serving.knative.dev]
  resources: ['services']
  verbs: ['*']
- apiGroups: [servicebinding.io]
  resources: ['servicebindings']
  verbs: ['*']
- apiGroups: [services.apps.tanzu.vmware.com]
  resources: ['resourceclaims']
  verbs: ['*']
- apiGroups: [scanning.apps.tanzu.vmware.com]
  resources: ['imagescans', 'sourcescans']
  verbs: ['*']

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: default
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: default
subjects:
  - kind: ServiceAccount
    name: default

EOF

Tekton pipeline

A pipeline to be ran whenever the supply chain hits the stage of testing the source code. Save below into a yaml file for ex:  tekton-pipeline.yaml

apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: developer-defined-tekton-pipeline
  labels:
    apps.tanzu.vmware.com/pipeline: test      # (!) required
spec:
  params:
    - name: source-url                        # (!) required
    - name: source-revision                   # (!) required
  tasks:
    - name: test
      params:
        - name: source-url
          value: $(params.source-url)
        - name: source-revision
          value: $(params.source-revision)
      taskSpec:
        params:
          - name: source-url
          - name: source-revision
        steps:
          - name: test
            image: gradle
            script: |-
              cd `mktemp -d`
              wget -qO- $(params.source-url) | tar xvz
              ./mvnw test

Run the yaml file with following command in tap-install namespace:

kubectl apply -f tekton-pipeline.yaml -n tap-install
pipeline.tekton.dev/developer-defined-tekton-pipeline created

scan policy:

The ScanPolicy defines a set of rules to evaluate for a particular scan to consider the artifacts (image or source code) either compliant or not. When a ImageScan or SourceScan is created to run a scan, those reference a policy whose name must match the one below (scan-policy). Save below code into a yaml file, for ex: scanpolicy.yaml

apiVersion: scanning.apps.tanzu.vmware.com/v1beta1
kind: ScanPolicy
metadata:
  name: scan-policy
spec:
  regoFile: |
    package policies

    default isCompliant = false

    # Accepted Values: "Critical", "High", "Medium", "Low", "Negligible", "UnknownSeverity"
    violatingSeverities := ["Critical","High","UnknownSeverity"]
    ignoreCVEs := []

    contains(array, elem) = true {
      array[_] = elem
    } else = false { true }

    isSafe(match) {
      fails := contains(violatingSeverities, match.Ratings.Rating[_].Severity)
      not fails
    }

    isSafe(match) {
      ignore := contains(ignoreCVEs, match.Id)
      ignore
    }

    isCompliant = isSafe(input.currentVulnerability)
  • Run the yaml file with following command in tap-install namespace:
kubectl apply -f scanpolicy.yaml -n tap-install
scanpolicy.scanning.apps.tanzu.vmware.com/scan-policy created

Image Secret:

Regardless of the supply chain that a Workload goes through, there must be a secret in the developer namespace. This secret contains the credentials to be passed to

# Where <key>.json is the file downloaded earlier: 

kubectl create secret docker-registry image-secret --docker-server=gcr.io --docker-username=_json_key --docker-password="$(cat <key>.json)" -n tap-install

ScanTemplate

  • Create a file named ootb-supply-chain-basic-values.yaml that specifies the corresponding values to the properties you want to change.
grype:
  namespace: tap-install 
  targetImagePullSecret: registry-credentials
$  tanzu package install grype-scanner --package-name grype.scanning.apps.tanzu.vmware.com --version 1.0.0  --namespace tap-install -f ootb-supply-chain-basic-values.yaml
| Installing package 'grype.scanning.apps.tanzu.vmware.com'
/ Getting package metadata for 'grype.scanning.apps.tanzu.vmware.com'
| Creating service account 'grype-scanner-tap-install-sa'
/ Creating cluster admin role 'grype-scanner-tap-install-cluster-role'
/ Creating cluster role binding 'grype-scanner-tap-install-cluster-rolebinding'
| Creating secret 'grype-scanner-tap-install-values'
| Creating package resource
- Waiting for 'PackageInstall' reconciliation for 'grype-scanner'
\ 'PackageInstall' resource install status: Reconciling


 Added installed package 'grype-scanner'

Deploy Application

To deploy your application, you must download an accelerator, upload it on your Git repository of choice, and run a CLI command. Let me take you through the steps of downloading an accelerator through TAP-GUI:

  • From the Tanzu Application Platform GUI portal, click on Accelerators on the left side of the navigation bar to see the list of available accelerators.
  • Locate the Tanzu Java Web App accelerator, which is a sample Spring Boot web app, and click on Choose button.

  • In the Generate Accelerators prompt, replace the default value dev.local in the prefix for container image registry field with the registry in the form of SERVER-NAME/REPO-NAME. The SERVER-NAME/REPO-NAME must match what was specified for registry as part of the installation values for ootb_supply_chain_basic. Click NEXT STEP, verify the provided information, and click CREATE.

  • After the Task Activity processes are complete, click on the DOWNLOAD ZIP FILE button

  • After downloading the zip file, expand it in a workspace directory and follow your preferred procedure for uploading the generated project files to a Git repository for your new project.
# Unzip the downloaded file and follow below process to push to git repo: 

reddye@reddye-a02 tanzu-java-web-app-demo % ls
LICENSE			Tiltfile		catalog-info.yaml	mvnw			pom.xml
README.md		accelerator-log.md	config			mvnw.cmd		src

# Initialize git

reddye@reddye-a02 tanzu-java-web-app-demo % git init

Initialized empty Git repository in /Users/reddye/Downloads/tanzu-java-web-app-demo/.git/

#Add all the files
git add *

#Commit
git commit -am "First commit"

git branch -M main

# Created a new repo named tanzu-java-web-app-demo in my github account and later executed below command: 

git remote add origin https://github.com/Eknathreddy09/tanzu-java-web-app-demo.git

# Syntax: git config --global user.email "github account email"

git config --global user.email "eknath.reddy09@gmail.com"

$ git push -u origin main
Username for 'https://github.com': eknathreddy09
Password for 'https://eknathreddy09@github.com':
Enumerating objects: 28, done.
Counting objects: 100% (28/28), done.
Delta compression using up to 16 threads
Compressing objects: 100% (19/19), done.
Writing objects: 100% (28/28), 15.36 KiB | 3.07 MiB/s, done.
Total 28 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/Eknathreddy09/tanzu-java-web-app-demo.git
 * [new branch]      main -> main
Branch 'main' set up to track remote branch 'main' from 'origin'.

# Login to Github account and verify the repo. 

For this demo, instead of downloading from accelerator, I have a used a public version to test which is available here

  • Deploy the Tanzu Java Web App accelerator by running the tanzu apps workload create command:
$ tanzu apps workload create tanzu-java-web-app  --git-repo https://github.com/sample-accelerators/tanzu-java-web-app --git-branch main --type web --label apps.tanzu.vmware.com/has-tests=true --label app.kubernetes.io/part-of=tanzu-java-web-app  --type web -n tap-install --yes

# View the build and runtime logs for your app by running the tail command:

tanzu apps workload tail tanzu-java-web-app --since 10m --timestamp -n tap-install

# Get the status of application: 

tanzu apps workload get tanzu-java-web-app -n tap-install
# tanzu-java-web-app: Ready
---
lastTransitionTime: "2022-01-17T06:04:43Z"
message: ""
reason: Ready
status: "True"
type: Ready

Workload pods
NAME                                                   STATE       AGE
tanzu-java-web-app-00001-deployment-86c664cc87-6mmbj   Running     8s
tanzu-java-web-app-build-1-build-pod                   Succeeded   56m
tanzu-java-web-app-config-writer-tnsfd-pod             Succeeded   51m
tanzu-java-web-app-p6mb2-test-pod                      Succeeded   61m

Workload Knative Services
NAME                 READY   URL
tanzu-java-web-app   Ready   http://tanzu-java-web-app.tap-install.example.com
  • Collect the External IP of Envoy service in name space: tanzu-system-ingress
kubectl get svc envoy -n tanzu-system-ingress
NAME    TYPE           CLUSTER-IP    EXTERNAL-IP   PORT(S)                      AGE
envoy   LoadBalancer   10.32.4.176   35.244.7.37   80:32521/TCP,443:30574/TCP   103m
  • Add an entry in your local machine /etc/hosts with the IP collected above pointing to hostname: tanzu-java-web-app.tap-install.example.com

  • Access the url tanzu-java-web-app.tap-install.example.com and you should see result as below:

Verify the scan results:

#Source scan 

kubectl get sourcescan -n tap-install
NAME                 PHASE       SCANNEDREVISION                            SCANNEDREPOSITORY                                                                                                                                      AGE   CRITICAL   HIGH   MEDIUM   LOW   UNKNOWN   CVETOTAL
tanzu-java-web-app   Completed   90bd107ad26e228886e2ad28c964580858196376   http://source-controller.flux-system.svc.cluster.local./gitrepository/tap-install/tanzu-java-web-app/90bd107ad26e228886e2ad28c964580858196376.tar.gz   66m

# Image Scan

kubectl get imagescan -n tap-install
NAME                 PHASE       SCANNEDIMAGE                                                                                                                            AGE   CRITICAL   HIGH   MEDIUM   LOW   UNKNOWN   CVETOTAL
tanzu-java-web-app   Completed   gcr.io/eknath-se/build-service/tanzu-java-web-app-tap-install@sha256:067cba3141828775b0715f5054f54db33c6b5193c77fc90a31100abcf5f83a71   61m