DevOps
Use Skaffold for local development in simple steps
This blog is a comprehensive guide to build and deploy apps to Kubernetes with Skaffold. This tool provides simple workflow for local development of apps
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
- Skaffold pipeline options can be used to build GitOps workflows
- Skaffold is built as lightweight tool (binary or docker image) with opinionated options to use in Dev or external CI/CD components.
- It detects changes in your source code and handles the pipeline to build, push, test and deploy your application automatically
- It presents simplistic way for logging and port-forwarding for local and incremental app Development
- 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-myclusterSwitched to context "kind-mycluster".$ kubectl config current-contextkind-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 initapiVersion: skaffold/v2beta26kind: Configmetadata: name: helloappjavabuild: artifacts: - image: helloappjava buildpacks: builder: gcr.io/buildpacks/builder:v1deploy: kubectl: manifests: - kubernetes/deployment.yml - kubernetes/service.yml? Do you want to write this configuration to skaffold.yaml? YesConfiguration skaffold.yaml was writtenYou can now run [skaffold build] to build the artifactsor [skaffold run] to build and deployor [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): 8080apiVersion: skaffold/v2beta26kind: Configmetadata: name: helloappjavabuild: artifacts: - image: pom-xml-image buildpacks: builder: gcr.io/buildpacks/builder:v1deploy: kubectl: manifests: - deployment.yamlportForward:- resourceType: service resourceName: pom-xml-image port: 8080deployment.yaml - apiVersion: v1kind: Servicemetadata: name: pom-xml-image labels: app: pom-xml-imagespec: ports: - port: 8080 protocol: TCP clusterIP: None selector: app: pom-xml-image---apiVersion: apps/v1kind: Deploymentmetadata: name: pom-xml-image labels: app: pom-xml-imagespec: 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? YesGenerated manifest deployment.yaml was writtenConfiguration skaffold.yaml was written$ cat skaffold.yamlapiVersion: skaffold/v2beta26kind: Configmetadata: name: helloappjavabuild: artifacts: - image: pom-xml-image buildpacks: builder: gcr.io/buildpacks/builder:v1deploy: kubectl: manifests: - deployment.yamlportForward:- 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.yamlapiVersion: skaffold/v2beta26kind: Configmetadata: name: helloappjavabuild: 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 devListing files to watch... - pom-xml-imageGenerating tags... - pom-xml-image -> pom-xml-image:dafd051-dirtyChecking cache... - pom-xml-image: Not found. BuildingStarting build...Found [kind-mycluster] context, using local docker daemon.Building [pom-xml-image]...base: Pulling from paketobuildpacks/builder4f4fb700ef54: Pull completeDigest: sha256:7308b52dc7dfd9ef2d1874ae905b44d489b1867bdb9c3c6fd21c28f941437abdStatus: Downloaded newer image for paketobuildpacks/builder:basebase-cnb: Pulling from paketobuildpacks/runbf99a8b93828: Already exists1d5075f6f6b5: Pull completeDigest: sha256:50c74ebc95c169ea54ee4650d39d3c71d0fc738a3f0ad89b23d646cfd9ea36f2Status: Downloaded newer image for paketobuildpacks/run:base-cnb0.13.2: Pulling from buildpacksio/lifecycleee78fc634fdc: Pull completeDigest: sha256:b7f2b9f44525ed0e962bdf5be186a266e36adc502b3dc0595dd2aac7f2b92e75Status: 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:ae0f87dd22a69604b1ff2c89089f4740eceec74fa357704367283b1a76ce9e9fStarting deploy...Loading images into kind cluster nodes... - pom-xml-image:ae0f87dd22a69604b1ff2c89089f4740eceec74fa357704367283b1a76ce9e9f -> LoadedImages loaded in 16.666 seconds - service/pom-xml-image created - deployment.apps/pom-xml-image createdWaiting for deployments to stabilize... - deployment/pom-xml-image is ready.Deployments stabilized in 2.193 secondsPort forwarding service/pom-xml-image in namespace default, remote port 8080 -> http://127.0.0.1:8080Press Ctrl+C to exitWatching 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 DockerfileFROM openjdk:17-jdk-alpineMAINTAINER txconsole.comCOPY target/*.jar app.jarENTRYPOINT ["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.yamlapiVersion: skaffold/v2beta26kind: Configmetadata: name: helloappjavabuild: artifacts: - image: helloappjava docker: dockerfile: Dockerfiledeploy: kubectl: manifests: - kubernetes/deployment.yml - kubernetes/service.ymlportForward:- 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 devListing files to watch... - helloappjavaGenerating tags... - helloappjava -> helloappjava:dafd051-dirtyChecking cache... - helloappjava: Not found. BuildingStarting build...Found [kind-mycluster] context, using local docker daemon.Building [helloappjava]...Sending build context to Docker daemon 21.86MBStep 1/4 : FROM openjdk:17-jdk-alpine17-jdk-alpine: Pulling from library/openjdk5843afab3874: Already exists53c9466125e4: Already existsd8d715783b80: Already existsDigest: sha256:4b6abae565492dbe9e7a894137c966a7485154238902f2f25e9dbd9784383d81Status: Downloaded newer image for openjdk:17-jdk-alpine ---> 264c9bdce361Step 2/4 : MAINTAINER txconsole.com ---> Running in f74d926c5c18 ---> fefd8b6517baStep 3/4 : COPY target/*.jar app.jar ---> 1792c8647de7Step 4/4 : ENTRYPOINT ["java","-jar","/app.jar"] ---> Running in f68ddae3ef09 ---> 60bf6d822209Successfully built 60bf6d822209Successfully tagged helloappjava:dafd051-dirtyTags used in deployment: - helloappjava -> helloappjava:60bf6d8222092bb2ef141dddf5425b8311412b0d869c7aa68bc2ca6855d23fb4Starting deploy...Loading images into kind cluster nodes... - helloappjava:60bf6d8222092bb2ef141dddf5425b8311412b0d869c7aa68bc2ca6855d23fb4 -> LoadedImages loaded in 18.094 seconds - deployment.apps/helloappjava created - service/helloappjava createdWaiting for deployments to stabilize... - helloapp:deployment/helloappjava is ready.Deployments stabilized in 2.188 secondsPress Ctrl+C to exitWatching 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.