Use Skaffold for local development in simple steps

14 January 2022

16 min read

Share
Author

This blog is a comprehensive guide to build and deploy apps to Kubernetes with Skaffold. This tool provides simple workflow to use for local development of apps. It’s portable tool and can be used to automate SDLC use cases for modules in an application. Skaffold has got multiple options to choose for building apps in different programming languages and to follow various deployment models.

Skaffold is an open source project from Google.

Awesome features of Skaffold

Skaffold is built with many devops workflow options

  • End-to-end Pipelines options:
    • run Run a pipeline
    • dev Run a pipeline in development mode
    • debug Run a pipeline in debug mode
  • Pipeline Building Blocks options:
    • build Build the artifacts
    • test Run tests against your built application images
    • deploy Deploy pre-built artifacts
    • delete Delete any resources deployed by Skaffold
    • render Perform all image builds, and output rendered Kubernetes manifests
    • apply Apply hydrated manifests to a cluster
  1. Skaffold pipeline options can be used to build GitOps workflows
  2. Skaffold is built as lightweight tool (binary or docker image) with opinionated options to use in Dev or external CI/CD components.
  3. It detects changes in your source code and handles the pipeline to build, push, test and deploy your application automatically
  4. It presents simplistic way for logging and port-forwarding for local and incremental app Development
  5. Skaffold is packaged with many devops value adding components like profiles, local use config, environment variables and build/deploy options

Initial setup

Install Docker: We have installed Docker (Version: 20.10.11) in the local workstation ( which is running mac os ). Install Skaffold CLI

The latest stable binaries can be found here. The install step for Mac OS :

curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/latest/skaffold-darwin-amd64 && \
sudo install skaffold /usr/local/bin/

Note: Skaffold can also be installed with Homebrew.

brew install skaffold

Install kubectl – Kubectl can be installed on workstation following instructions here. install kubernetes cluster or connect to existing cluster.

For local development, we can use either minikube or kind kubernetes cluster or docker based Kubernetes cluster.

NOTE: For devops or production use case, we can configure the current-context of kubectl on the workstation with an existing production grade kubernetes cluster.(eg: Tanzu kubernetes Grid, Openshift or Rancher).

$ k config use-context kind-mycluster
Switched to context "kind-mycluster".
 
$ kubectl config current-context
kind-mycluster

Note: For the current POC’s, we would be using kind cluster to connect deploy the artifacts.

An application with source-code to build and deploy for various Skaffold use cases.

We would use the Helloappjava to build and deploy in various example. Detailed steps to build and maintain this application is demonstrated here.

skaffold init

To configure any project with Skaffold, need to provide it’s config in base classpath location. These configs are mentioned in skaffold.yaml. For a new project, we can let Skaffold config get created automatically with build and deploy stages (as a minimum config).

To configure the project with Skaffold, we invoke skaffold init.

$ skaffold init
apiVersion: skaffold/v2beta26
kind: Config
metadata:
  name: helloappjava
build:
  artifacts:
  - image: helloappjava
    buildpacks:
      builder: gcr.io/buildpacks/builder:v1
deploy:
  kubectl:
    manifests:
    - kubernetes/deployment.yml
    - kubernetes/service.yml
 
? Do you want to write this configuration to skaffold.yaml? Yes
Configuration skaffold.yaml was written
You can now run [skaffold build] to build the artifacts
or [skaffold run] to build and deploy
or [skaffold dev] to enter development mode, with auto-redeploy

This project does not contain any Dockerfile yet, thus Skaffold has picked buildpack as the default build mechanism for this project.

$ tree -L 4 -C .
.
├── README.md
├── kubernetes
   ├── deployment.yml
   └── service.yml
├── pom.xml
└── src
    ├── main
       ├── java
          └── com
       └── resources
           ├── application.properties
           ├── static
           └── templates
    └── test
        └── java
            └── com

NOTE: For a detailed discussion on buildpack, we have another blog for reference. In my opinion, buildpack is an awesome choice for building apps and it comes with many features better then Dockerfile. So, unless we are working on an existing app, it’s always better to prepare to build apps with buildpack.

  • Skaffold scans through the project and identifies the Kubernetes resources for deploy stage.
  • We can override all the configs and also add more options and stages based on our need.
  • In case of conflicts where more then single build and deploy mechanism can be used for the project, we can provide the chosen option via below params
 
--artifact='{"builder":"Docker","payload":{"path":"/web/Dockerfile.web"},"image":"gcr.io/web-project/image"}')  
--compose-file='': Initialize from a docker-compose file
--default-kustomization='': Default Kustomization overlay path
--kubernetes-manifest=[]: A path or a glob pattern to kubernetes manifests (can be non-existent) to be added to the kubectl deployer (overrides detection of kubernetes manifests)

Instead of providing kubernetes resources, we can let them auto-generated with --generate-manifests=true option. For this change, we have optionally removed the Kubernetes resources from the source folder.

 
$ skaffold init --generate-manifests=true
? Select port to forward for pom-xml-image (leave blank for none): 8080
apiVersion: skaffold/v2beta26
kind: Config
metadata:
  name: helloappjava
build:
  artifacts:
  - image: pom-xml-image
    buildpacks:
      builder: gcr.io/buildpacks/builder:v1
deploy:
  kubectl:
    manifests:
    - deployment.yaml
portForward:
- resourceType: service
  resourceName: pom-xml-image
  port: 8080
 
deployment.yaml - apiVersion: v1
kind: Service
metadata:
  name: pom-xml-image
  labels:
    app: pom-xml-image
spec:
  ports:
  - port: 8080
    protocol: TCP
  clusterIP: None
  selector:
    app: pom-xml-image
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pom-xml-image
  labels:
    app: pom-xml-image
spec:
  replicas: 1
  selector:
    matchLabels:
      app: pom-xml-image
  template:
    metadata:
      labels:
        app: pom-xml-image
    spec:
      containers:
      - name: pom-xml-image
        image: pom-xml-image
 
? Do you want to write this configuration, along with the generated k8s manifests, to skaffold.yaml? Yes
Generated manifest deployment.yaml was written
Configuration skaffold.yaml was written
 
$ cat skaffold.yaml
apiVersion: skaffold/v2beta26
kind: Config
metadata:
  name: helloappjava
build:
  artifacts:
  - image: pom-xml-image
    buildpacks:
      builder: gcr.io/buildpacks/builder:v1
deploy:
  kubectl:
    manifests:
    - deployment.yaml
portForward:
- resourceType: service
  resourceName: pom-xml-image
  port: 8080

The deployment and service resource is created within the deployment.yaml file in project base location and added into the Scaffold config. Also, Skaffold is smart enough to add the port-forward stage to expose the service to localhost for developer’s convenience.

Local development

We can use skaffold dev for continuous build & deploy on code changes. For local development, we assume that docker would be installed on same workstation as the source-code and the kubernetes cluster would be configured.

skaffold dev analyse the source code to identify the config files and app language. It watches the source code and executes Skaffold pipeline every time a change is detected.

App build with buildpack

NOTE: The helloappjava application, is built with Java version 17, and the default builder image used by buildpack is configured for Java version 11. Thus, we have to do a modification to the skaffold.yaml to change the builder and runimage as mentioned below :

$ cat skaffold.yaml
apiVersion: skaffold/v2beta26
kind: Config
metadata:
  name: helloappjava
build:
  artifacts:
  - image: pom-xml-image
    buildpacks:
      builder: paketobuildpacks/builder:base
      runImage: paketobuildpacks/run:base-cnb
      env:
      - "BP_JVM_VERSION=17"
...

We can use the skaffold.yaml to perform skaffold dev now :

 
$ skaffold dev
Listing files to watch...
 - pom-xml-image
Generating tags...
 - pom-xml-image -> pom-xml-image:dafd051-dirty
Checking cache...
 - pom-xml-image: Not found. Building
Starting build...
Found [kind-mycluster] context, using local docker daemon.
Building [pom-xml-image]...
base: Pulling from paketobuildpacks/builder
4f4fb700ef54: Pull complete
Digest: sha256:7308b52dc7dfd9ef2d1874ae905b44d489b1867bdb9c3c6fd21c28f941437abd
Status: Downloaded newer image for paketobuildpacks/builder:base
base-cnb: Pulling from paketobuildpacks/run
bf99a8b93828: Already exists
1d5075f6f6b5: Pull complete
Digest: sha256:50c74ebc95c169ea54ee4650d39d3c71d0fc738a3f0ad89b23d646cfd9ea36f2
Status: Downloaded newer image for paketobuildpacks/run:base-cnb
0.13.2: Pulling from buildpacksio/lifecycle
ee78fc634fdc: Pull complete
Digest: sha256:b7f2b9f44525ed0e962bdf5be186a266e36adc502b3dc0595dd2aac7f2b92e75
Status: Downloaded newer image for buildpacksio/lifecycle:0.13.2
===> DETECTING
[detector] 8 of 19 buildpacks participating
[detector] paketo-buildpacks/ca-certificates   3.0.2
[detector] paketo-buildpacks/bellsoft-liberica 9.0.2
[detector] paketo-buildpacks/syft              1.3.1
[detector] paketo-buildpacks/maven             6.0.2
[detector] paketo-buildpacks/executable-jar    6.0.2
[detector] paketo-buildpacks/apache-tomcat     7.0.3
[detector] paketo-buildpacks/dist-zip          5.0.2
[detector] paketo-buildpacks/spring-boot       5.3.0
===> ANALYZING
[analyzer] Previous image with name "pom-xml-image:latest" not found
===> RESTORING
===> BUILDING
[builder] Paketo CA Certificates Buildpack 3.0.2
[builder]   https://github.com/paketo-buildpacks/ca-certificates
[builder]   Launch Helper: Contributing to layer
[builder]     Creating /layers/paketo-buildpacks_ca-certificates/helper/exec.d/ca-certificates-helper
[builder] Paketo BellSoft Liberica Buildpack 9.0.2
[builder]   https://github.com/paketo-buildpacks/bellsoft-liberica
[builder]   Build Configuration:
[builder]     $BP_JVM_TYPE                 JRE             the JVM type - JDK or JRE
[builder]     $BP_JVM_VERSION              17              the Java version
[builder]   Launch Configuration:
[builder]     $BPL_DEBUG_ENABLED           false           enables Java remote debugging support
[builder]     $BPL_DEBUG_PORT              8000            configure the remote debugging port
[builder]     $BPL_DEBUG_SUSPEND           false           configure whether to suspend execution until a debugger has attached
[builder]     $BPL_HEAP_DUMP_PATH                          write heap dumps on error to this path
[builder]     $BPL_JAVA_NMT_ENABLED        true            enables Java Native Memory Tracking (NMT)
[builder]     $BPL_JAVA_NMT_LEVEL          summary         configure level of NMT, summary or detail
[builder]     $BPL_JFR_ARGS                                configure custom Java Flight Recording (JFR) arguments
[builder]     $BPL_JFR_ENABLED             false           enables Java Flight Recording (JFR)
[builder]     $BPL_JMX_ENABLED             false           enables Java Management Extensions (JMX)
[builder]     $BPL_JMX_PORT                5000            configure the JMX port
[builder]     $BPL_JVM_HEAD_ROOM           0               the headroom in memory calculation
[builder]     $BPL_JVM_LOADED_CLASS_COUNT  35% of classes  the number of loaded classes in memory calculation
[builder]     $BPL_JVM_THREAD_COUNT        250             the number of threads in memory calculation
[builder]     $JAVA_TOOL_OPTIONS                           the JVM launch flags
[builder]   BellSoft Liberica JDK 17.0.1: Contributing to layer
[builder]     Downloading from https://github.com/bell-sw/Liberica/releases/download/17.0.1+12/bellsoft-jdk17.0.1+12-linux-amd64.tar.gz
[builder]     Verifying checksum
[builder]     Expanding to /layers/paketo-buildpacks_bellsoft-liberica/jdk
[builder]     Adding 128 container CA certificates to JVM truststore
[builder]     Writing env.build/JAVA_HOME.override
[builder]     Writing env.build/JDK_HOME.override
[builder]   BellSoft Liberica JRE 17.0.1: Contributing to layer
[builder]     Downloading from https://github.com/bell-sw/Liberica/releases/download/17.0.1+12/bellsoft-jre17.0.1+12-linux-amd64.tar.gz
[builder]     Verifying checksum
[builder]     Expanding to /layers/paketo-buildpacks_bellsoft-liberica/jre
[builder]     Adding 128 container CA certificates to JVM truststore
[builder]     Writing env.launch/BPI_APPLICATION_PATH.default
[builder]     Writing env.launch/BPI_JVM_CACERTS.default
[builder]     Writing env.launch/BPI_JVM_CLASS_COUNT.default
[builder]     Writing env.launch/BPI_JVM_SECURITY_PROVIDERS.default
[builder]     Writing env.launch/JAVA_HOME.default
[builder]     Writing env.launch/JAVA_TOOL_OPTIONS.append
[builder]     Writing env.launch/JAVA_TOOL_OPTIONS.delim
[builder]     Writing env.launch/MALLOC_ARENA_MAX.default
[builder]   Launch Helper: Contributing to layer
[builder]     Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/active-processor-count
[builder]     Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/java-opts
[builder]     Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/jvm-heap
[builder]     Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/link-local-dns
[builder]     Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/memory-calculator
[builder]     Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/openssl-certificate-loader
[builder]     Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/security-providers-configurer
[builder]     Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/jmx
[builder]     Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/jfr
[builder]     Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/nmt
[builder]     Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/security-providers-classpath-9
[builder]     Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/debug-9
[builder]   Java Security Properties: Contributing to layer
[builder]     Writing env.launch/JAVA_SECURITY_PROPERTIES.default
[builder]     Writing env.launch/JAVA_TOOL_OPTIONS.append
[builder]     Writing env.launch/JAVA_TOOL_OPTIONS.delim
[builder] Paketo Syft Buildpack 1.3.1
[builder]   https://github.com/paketo-buildpacks/syft
[builder]     Downloading from https://github.com/anchore/syft/releases/download/v0.33.0/syft_0.33.0_linux_amd64.tar.gz
[builder]     Verifying checksum
[builder]     Writing env.build/SYFT_CHECK_FOR_APP_UPDATE.default
[builder] Paketo Maven Buildpack 6.0.2
[builder]   https://github.com/paketo-buildpacks/maven
[builder]   Build Configuration:
[builder]     $BP_MAVEN_BUILD_ARGUMENTS  -Dmaven.test.skip=true package  the arguments to pass to Maven
[builder]     $BP_MAVEN_BUILT_ARTIFACT   target/*.[ejw]ar                the built application artifact explicitly.  Supersedes $BP_MAVEN_BUILT_MODULE
[builder]     $BP_MAVEN_BUILT_MODULE                                     the module to find application artifact in
[builder]     $BP_MAVEN_POM_FILE         pom.xml                         the location of the main pom.xml file, relative to the application root
[builder]     Creating cache directory /home/cnb/.m2
[builder]   Compiled Application: Contributing to layer
[builder]     Executing mvnw --batch-mode -Dmaven.test.skip=true package
[builder]       [INFO] Scanning for projects...
[builder]       [INFO] ---------------------< com.txconsole:helloappjava >---------------------
[builder]       [INFO] Building helloappjava 0.0.1-SNAPSHOT
[builder]       [INFO] --------------------------------[ jar ]---------------------------------
[builder]       [INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-maven-plugin/2.6.2/spring-boot-maven-plugin-2.6.2.pom
[builder]       [INFO] Replacing main artifact with repackaged archive
[builder]       [INFO] ------------------------------------------------------------------------
[builder]       [INFO] BUILD SUCCESS
[builder]       [INFO] ------------------------------------------------------------------------
[builder]       [INFO] Total time:  05:50 min
[builder]       [INFO] Finished at: 2021-12-29T09:22:19Z
[builder]       [INFO] ------------------------------------------------------------------------
[builder]         Removing source code
[builder] Paketo Executable JAR Buildpack 6.0.2
[builder]   https://github.com/paketo-buildpacks/executable-jar
[builder]   Class Path: Contributing to layer
[builder]     Writing env/CLASSPATH.delim
[builder]     Writing env/CLASSPATH.prepend
[builder]   Process types:
[builder]     executable-jar: java org.springframework.boot.loader.JarLauncher (direct)
[builder]     task:           java org.springframework.boot.loader.JarLauncher (direct)
[builder]     web:            java org.springframework.boot.loader.JarLauncher (direct)
[builder] Paketo Spring Boot Buildpack 5.3.0
[builder]   https://github.com/paketo-buildpacks/spring-boot
[builder]   Creating slices from layers index
[builder]     dependencies
[builder]     spring-boot-loader
[builder]     snapshot-dependencies
[builder]     application
[builder]   Launch Helper: Contributing to layer
[builder]     Creating /layers/paketo-buildpacks_spring-boot/helper/exec.d/spring-cloud-bindings
[builder]   Spring Cloud Bindings 1.8.0: Contributing to layer
[builder]     Downloading from https://repo.spring.io/release/org/springframework/cloud/spring-cloud-bindings/1.8.0/spring-cloud-bindings-1.8.0.jar
[builder]     Verifying checksum
[builder]     Copying to /layers/paketo-buildpacks_spring-boot/spring-cloud-bindings
[builder]   Web Application Type: Contributing to layer
[builder]     Servlet web application detected
[builder]     Writing env.launch/BPL_JVM_THREAD_COUNT.default
[builder]   4 application slices
[builder]   Image labels:
[builder]     org.opencontainers.image.title
[builder]     org.opencontainers.image.version
[builder]     org.springframework.boot.version
===> EXPORTING
[exporter] Setting default process type 'web'
[exporter] Saving pom-xml-image:latest...
[exporter] *** Images (ae0f87dd22a6):
[exporter]       pom-xml-image:latest
[exporter] Adding cache layer 'paketo-buildpacks/bellsoft-liberica:jdk'
[exporter] Adding cache layer 'paketo-buildpacks/syft:syft'
[exporter] Adding cache layer 'paketo-buildpacks/maven:application'
[exporter] Adding cache layer 'paketo-buildpacks/maven:cache'
Tags used in deployment:
 - pom-xml-image -> pom-xml-image:ae0f87dd22a69604b1ff2c89089f4740eceec74fa357704367283b1a76ce9e9f
Starting deploy...
Loading images into kind cluster nodes...
 - pom-xml-image:ae0f87dd22a69604b1ff2c89089f4740eceec74fa357704367283b1a76ce9e9f -> Loaded
Images loaded in 16.666 seconds
 - service/pom-xml-image created
 - deployment.apps/pom-xml-image created
Waiting for deployments to stabilize...
 - deployment/pom-xml-image is ready.
Deployments stabilized in 2.193 seconds
Port forwarding service/pom-xml-image in namespace default, remote port 8080 -> http://127.0.0.1:8080
Press Ctrl+C to exit
Watching for changes...
[pom-xml-image] Setting Active Processor Count to 4
[pom-xml-image] WARNING: Unable to convert memory limit "max" from path "/sys/fs/cgroup/memory.max" as int: memory size "max" does not match pattern "^([\\d]+)([kmgtKMGT]?)$"
[pom-xml-image] Calculating JVM memory based on 953836K available memory
[pom-xml-image] Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx362738K -XX:MaxMetaspaceSize=79097K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 953836K, Thread Count: 250, Loaded Class Count: 11551, Headroom: 0%)
[pom-xml-image] Enabling Java Native Memory Tracking
[pom-xml-image] Adding 128 container CA certificates to JVM truststore
[pom-xml-image] Spring Cloud Bindings Enabled
[pom-xml-image] Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_bellsoft-liberica/java-security-properties/java-security.properties -XX:+ExitOnOutOfMemoryError -XX:ActiveProcessorCount=4 -XX:MaxDirectMemorySize=10M -Xmx362738K -XX:MaxMetaspaceSize=79097K -XX:ReservedCodeCacheSize=240M -Xss1M -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary -XX:+PrintNMTStatistics -Dorg.springframework.cloud.bindings.boot.enable=true
[pom-xml-image]
[pom-xml-image]   .   ____          _            __ _ _
[pom-xml-image]  /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
[pom-xml-image] ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
[pom-xml-image]  \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
[pom-xml-image]   '  |____| .__|_| |_|_| |_\__, | / / / /
[pom-xml-image]  =========|_|==============|___/=/_/_/_/
[pom-xml-image]  :: Spring Boot ::                (v2.6.2)
[pom-xml-image]
[pom-xml-image] 2021-12-29 09:23:01.286  INFO 1 --- [           main] c.t.h.HelloappjavaApplication            : Starting HelloappjavaApplication v0.0.1-SNAPSHOT using Java 17.0.1 on pom-xml-image-57dbcd56f7-h4npq with PID 1 (/workspace/BOOT-INF/classes started by cnb in /workspace)
[pom-xml-image] 2021-12-29 09:23:01.289  INFO 1 --- [           main] c.t.h.HelloappjavaApplication            : No active profile set, falling back to default profiles: default
[pom-xml-image] 2021-12-29 09:23:03.127  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
[pom-xml-image] 2021-12-29 09:23:03.156  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
[pom-xml-image] 2021-12-29 09:23:03.157  INFO 1 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.56]
[pom-xml-image] 2021-12-29 09:23:03.281  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
[pom-xml-image] 2021-12-29 09:23:03.282  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1919 ms
[pom-xml-image] 2021-12-29 09:23:03.829  INFO 1 --- [           main] o.s.b.a.w.s.WelcomePageHandlerMapping    : Adding welcome page template: index
[pom-xml-image] 2021-12-29 09:23:04.166  INFO 1 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 13 endpoint(s) beneath base path '/manage'
[pom-xml-image] 2021-12-29 09:23:04.243  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
[pom-xml-image] 2021-12-29 09:23:04.268  INFO 1 --- [           main] c.t.h.HelloappjavaApplication            : Started HelloappjavaApplication in 3.761 seconds (JVM running for 4.696)
[pom-xml-image] 2021-12-29 09:23:31.500  INFO 1 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
[pom-xml-image] 2021-12-29 09:23:31.501  INFO 1 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
[pom-xml-image] 2021-12-29 09:23:31.506  INFO 1 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 5 ms
[pom-xml-image] 2021-12-29 09:23:31.577  INFO 1 --- [nio-8080-exec-1] c.t.h.controllers.GreetingController     : LOG: index - {greet=Hello, name=KingKong, appversion=v1.0.0}
^CCleaning up...
 - service "pom-xml-image" deleted
 - deployment.apps "pom-xml-image" deleted

App build with Dockerfile

To test the app build with Dockerfile, we have added Dockerfile to the source root folder.

$ cat Dockerfile
FROM openjdk:17-jdk-alpine
MAINTAINER txconsole.com
COPY target/*.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

We can remove the existing skaffold.yaml (if any) and now, we can trigger Skaffold init, to start from scratch and create new Skaffold config for the project. It would recognize that the app can be built in 2 ways – Dockerfile and Buildpack. Thus, present an option for the developer to choose the option.

$ skaffold init
? Choose the builder to build image helloappjava  [Use arrows to move, type to filter]
> Buildpacks (pom.xml)
  Docker (Dockerfile)
  None (image not built from these sources)

NOTE: The above build selection can be overridden with skaffold init --artifact='{"builder":"Docker","payload":{"path":"Dockerfile"},"image":"helloappjava"}'.

Since we are using kind cluster without any ingress, we can add another stage for port-forwarding the service, so that developer can access the application on the localhost after it’s deployed.

$ cat skaffold.yaml
apiVersion: skaffold/v2beta26
kind: Config
metadata:
  name: helloappjava
build:
  artifacts:
  - image: helloappjava
    docker:
      dockerfile: Dockerfile
deploy:
  kubectl:
    manifests:
    - kubernetes/deployment.yml
    - kubernetes/service.yml
portForward:
- resourceType: service
  resourceName: helloappjava
  port: 8080

Since we are working with java project which is built with maven, so we can run mvn clean package prior to executing skaffold to pick the right *.jar for image build and deploy purpose.

We can safely trigger the build and deploy of the application locally.

 
$ skaffold dev
Listing files to watch...
 - helloappjava
Generating tags...
 - helloappjava -> helloappjava:dafd051-dirty
Checking cache...
 - helloappjava: Not found. Building
Starting build...
Found [kind-mycluster] context, using local docker daemon.
Building [helloappjava]...
Sending build context to Docker daemon  21.86MB
Step 1/4 : FROM openjdk:17-jdk-alpine
17-jdk-alpine: Pulling from library/openjdk
5843afab3874: Already exists
53c9466125e4: Already exists
d8d715783b80: Already exists
Digest: sha256:4b6abae565492dbe9e7a894137c966a7485154238902f2f25e9dbd9784383d81
Status: Downloaded newer image for openjdk:17-jdk-alpine
 ---> 264c9bdce361
Step 2/4 : MAINTAINER txconsole.com
 ---> Running in f74d926c5c18
 ---> fefd8b6517ba
Step 3/4 : COPY target/*.jar app.jar
 ---> 1792c8647de7
Step 4/4 : ENTRYPOINT ["java","-jar","/app.jar"]
 ---> Running in f68ddae3ef09
 ---> 60bf6d822209
Successfully built 60bf6d822209
Successfully tagged helloappjava:dafd051-dirty
Tags used in deployment:
 - helloappjava -> helloappjava:60bf6d8222092bb2ef141dddf5425b8311412b0d869c7aa68bc2ca6855d23fb4
Starting deploy...
Loading images into kind cluster nodes...
 - helloappjava:60bf6d8222092bb2ef141dddf5425b8311412b0d869c7aa68bc2ca6855d23fb4 -> Loaded
Images loaded in 18.094 seconds
 - deployment.apps/helloappjava created
 - service/helloappjava created
Waiting for deployments to stabilize...
 - helloapp:deployment/helloappjava is ready.
Deployments stabilized in 2.188 seconds
Press Ctrl+C to exit
Watching for changes...
[helloappjava]
[helloappjava]   .   ____          _            __ _ _
[helloappjava]  /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
[helloappjava] ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
[helloappjava]  \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
[helloappjava]   '  |____| .__|_| |_|_| |_\__, | / / / /
[helloappjava]  =========|_|==============|___/=/_/_/_/
[helloappjava]  :: Spring Boot ::                (v2.6.2)
[helloappjava]
[helloappjava] 2021-12-29 12:52:49.943  INFO 1 --- [           main] c.t.h.HelloappjavaApplication            : Starting HelloappjavaApplication v0.0.1-SNAPSHOT using Java 17-ea on helloappjava-7ccd79f4b6-s4z77 with PID 1 (/app.jar started by root in /)
[helloappjava] 2021-12-29 12:52:49.950  INFO 1 --- [           main] c.t.h.HelloappjavaApplication            : No active profile set, falling back to default profiles: default
[helloappjava] 2021-12-29 12:52:52.194  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
[helloappjava] 2021-12-29 12:52:52.211  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
[helloappjava] 2021-12-29 12:52:52.211  INFO 1 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.56]
[helloappjava] 2021-12-29 12:52:52.343  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
[helloappjava] 2021-12-29 12:52:52.344  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2299 ms
[helloappjava] 2021-12-29 12:52:53.408  INFO 1 --- [           main] o.s.b.a.w.s.WelcomePageHandlerMapping    : Adding welcome page template: index
[helloappjava] 2021-12-29 12:52:53.814  INFO 1 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 13 endpoint(s) beneath base path '/manage'
[helloappjava] 2021-12-29 12:52:53.875  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
[helloappjava] 2021-12-29 12:52:53.895  INFO 1 --- [           main] c.t.h.HelloappjavaApplication            : Started HelloappjavaApplication in 4.955 seconds (JVM running for 5.747)
^CCleaning up...
 - deployment.apps "helloappjava" deleted
 - service "helloappjava" deleted

Wrapping Up

We have seen 2 most common ways of building the application and deploying apps to kubernetes cluster. We have seen deploying apps to Kubernetes with kubectl which is the most common approach. Along with it, we can use other means to deploy to kubernetes – kustomize and helm. Since the topic of current discussion is for local development, thus kubectl would be a perfect fit for most of the use cases. Other deploy means can be used for production deploy or devops workflows.

Skaffold can also be used for production grade CI/CD workflow implementations which we would cover in another blog.