Skaffold: K8s Development Made Easy

Skaffold is a command line tool developed by Google, which aims to facilitate continuous development for Kubernetes applications. It will automate the task of building and deploying to a Kubernetes cluster whereas you, as a developer, can stay focused on writing code. It seems interesting enough to take a closer look at it!

Introduction

In November 2019 a generally available release was announced promising to save developers time by automating the development workflow. What will Skaffold do for us?

  1. It detects source changes while you are developing
  2. It automatically builds and creates your artifacts (your Docker image) based on a Dockerfile or Jib
  3. It tags the artifacts
  4. It pushes and deploys the artifacts to your Kubernetes cluster

In order to get acquainted with Skaffold, we will run a local Kubernetes cluster with the help of minikube and we will deploy by means of kubectl, the command line interface tool for Kubernetes.

It is advised to take a look at the official Skaffold documentation and examples for more in-depth information. The sources we are using in this post are available at GitHub.

Prerequisites

Before getting started, we need to install minikube, kubectl, and Skaffold if you have not done so already. We will be using Ubuntu 18.04.

Install Minikube

Installation of minikube (version 1.6.2) is quite easy when working with Linux. If you are working with Windows, please take a look at one of our previous posts, it was quite of a struggle way then, but maybe things have improved in the meanwhile.

First, check whether our system has virtualization support enabled:

Shell
 




xxxxxxxxxx
1


 
1
$ egrep -q 'vmx|svm' /proc/cpuinfo && echo yes || echo no 
2
yes



The output of the command is yes, which means we do not need to execute any extra steps.

Download and install minikube:

Shell
 




xxxxxxxxxx
1


 
1
$ curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube_1.6.2.deb && sudo dpkg -i minikube_1.6.2.deb



Start minikube:

Shell
 




xxxxxxxxxx
1
14


 
1
$ minikube start
2
minikube v1.6.2 on Ubuntu 18.04
3
Automatically selected the 'virtualbox' driver (alternates: [none])
4
Downloading VM boot image ...
5
> minikube-v1.6.0.iso.sha256: 65 B / 65 B [--------------] 100.00% ? p/s 0s
6
> minikube-v1.6.0.iso: 150.93 MiB / 150.93 MiB [-] 100.00% 8.44 MiB p/s 18s
7
Creating virtualbox VM (CPUs=2, Memory=2000MB, Disk=20000MB) ...
8
Preparing Kubernetes v1.17.0 on Docker '19.03.5' ...
9
Downloading kubelet v1.17.0
10
Downloading kubeadm v1.17.0
11
Pulling images ...
12
Launching Kubernetes ... 
13
Waiting for cluster to come online ...
14
Done! kubectl is now configured to use "minikube"



Install Kubectl

Installation instructions for kubectl can be found here. For Linux, we have to execute the steps below, with kubectl version we verify the successful installation:

Shell
 




xxxxxxxxxx
1


1
$ curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl
2
$ chmod +x ./kubectl
3
$ sudo mv ./kubectl /usr/local/bin/kubectl
4
$ kubectl version
5
Client Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.0", GitCommit:"70132b0f130acc0bed193d9ba59dd186f0e634cf", GitTreeState:"clean", BuildDate:"2019-12-07T21:20:10Z", GoVersion:"go1.13.4", Compiler:"gc", Platform:"linux/amd64"}
6
Server Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.0", GitCommit:"70132b0f130acc0bed193d9ba59dd186f0e634cf", GitTreeState:"clean", BuildDate:"2019-12-07T21:12:17Z", GoVersion:"go1.13.4", Compiler:"gc", Platform:"linux/amd64"}



Install Skaffold

Installation instructions for Skaffold can be found here. The instructions are very similar to the one for kubectl.

Shell
 




xxxxxxxxxx
1


 
1
$ curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/latest/skaffold-linux-amd64
2
$ chmod +x skaffold
3
$ sudo mv skaffold /usr/local/bin
4
$ skaffold version
5
v1.1.0



Create Demo Application

We will create a simple Spring Boot demo application. We use Spring MVC and create one REST endpoint, which will return a greeting message.

Our pom contains the following dependencies and plugins:

XML
 




xxxxxxxxxx
1
24


 
1
<dependencies>
2
  <dependency>
3
    <groupId>org.springframework.boot</groupId>
4
    <artifactId>spring-boot-starter-actuator</artifactId>
5
  </dependency>
6
  <dependency>
7
    <groupId>org.springframework.boot</groupId>
8
    <artifactId>spring-boot-starter-web</artifactId>
9
  </dependency>
10
  <dependency>
11
    <groupId>org.springframework.boot</groupId>
12
    <artifactId>spring-boot-starter-test</artifactId>
13
    <scope>test</scope>
14
  </dependency>
15
</dependencies>
16
 
17
<build>
18
  <plugins>
19
    <plugin>
20
      <groupId>org.springframework.boot</groupId>
21
      <artifactId>spring-boot-maven-plugin</artifactId>
22
    </plugin>
23
  </plugins>
24
</build>



The HelloController contains one method, which returns the greeting message and the address of the host where it is executed.

Java
 




xxxxxxxxxx
1
16


1
@RestController
2
public class HelloController {
3
 
4
    @RequestMapping("/hello")
5
    public String hello() {
6
        StringBuilder message = new StringBuilder("Hello Skaffold!");
7
        try {
8
            InetAddress ip = InetAddress.getLocalHost();
9
            message.append(" From host: " + ip);
10
        } catch (UnknownHostException e) {
11
            e.printStackTrace();
12
        }
13
        return message.toString();
14
    }
15
 
16
}



Use Skaffold

Now that we have finished all the necessary preparations, it is time to start using Skaffold. Currently, we deliberately have left out some important configuration, which Skaffold will need, but this will allow us to verify which error messages are returned and how to solve them.

Generate skaffold.yaml

Skaffold will need a skaffold.yaml file that contains the development workflow you want to use. The file can be generated when the init command is executed from within your project directory.

Shell
 




xxxxxxxxxx
1


 
1
$ skaffold init
2
FATA[0000] one or more valid Kubernetes manifests is required to run skaffold



Skaffold init does not create the Kubernetes manifest files for us, we will need to create them manually.

We will create a k8s deployment file with the help of kubectl. We copy the output of the command to file deployment.yaml in the k8s directory. The CLI parameter --dry-run ensures us that the deployment is not executed yet, the parameter -oyaml will output the configuration, which will allow us to just copy the contents.

Shell
 




xxxxxxxxxx
1
25


 
1
$ kubectl create deployment myskaffoldplanet --image=docker.io/mydeveloperplanet/myskaffoldplanet --dry-run -oyaml
2
apiVersion: apps/v1
3
kind: Deployment
4
metadata:
5
  creationTimestamp: null
6
  labels:
7
    app: myskaffoldplanet
8
    name: myskaffoldplanet
9
spec:
10
  replicas: 1
11
  selector:
12
    matchLabels:
13
      app: myskaffoldplanet
14
  strategy: {}
15
  template:
16
    metadata:
17
      creationTimestamp: null
18
      labels:
19
        app: myskaffoldplanet
20
    spec:
21
      containers:
22
      - image: docker.io/mydeveloperplanet/myskaffoldplanet
23
        name: myskaffoldplanet
24
        resources: {}
25
status: {}



Running skaffold init again, returns a new error:

Shell
 




xxxxxxxxxx
1


 
1
$ skaffold init
2
FATA[0000] one or more valid builder configuration (Dockerfile or Jib configuration) must be present to build images with skaffold; please provide at least one build config and try again or run `skaffold init --skip-build`



We could have expected this, because we did not provide a Dockerfile or Jib configuration. We will make use of Jib after our positive experiences with it in our previous post. Add the Jib Maven Plugin to our pom. We do not provide credentials this time, because we are not going to push the Docker image to a Docker registry.

XML
 




xxxxxxxxxx
1
21


 
1
<plugin>
2
  <groupId>com.google.cloud.tools</groupId>
3
  <artifactId>jib-maven-plugin</artifactId>
4
  <version>1.8.0</version>
5
  <configuration>
6
    <!-- openjdk:11.0.5-jre -->
7
    <from>
8
      <image>openjdk@sha256:b3e19d27caa8249aad6f90c6e987943d03e915bbf3a66bc1b7f994a4fed668f6</image>
9
    </from>
10
    <to>
11
      <image>docker.io/${docker.image.prefix}/${project.artifactId}</image>
12
      <tags>
13
        <tag>${project.version}</tag>
14
      </tags>
15
    </to>
16
    <container>
17
      <mainClass>com.mydeveloperplanet.myskaffoldplanet.MySkaffoldPlanetApplication</mainClass>
18
      <user>nobody</user>
19
    </container>
20
  </configuration>
21
</plugin>



In order to use Jib, we need to add the flag --XXenableJibInit, see also this issue.

Shell
 




xxxxxxxxxx
1
19


1
$ skaffold init --XXenableJibInit
2
apiVersion: skaffold/v2alpha1
3
kind: Config
4
metadata:
5
  name: myskaffoldplanet
6
build:
7
  artifacts:
8
  - image: docker.io/mydeveloperplanet/myskaffoldplanet
9
    jib: {}
10
deploy:
11
  kubectl:
12
    manifests:
13
    - k8s/deployment.yaml
14
 
15
Do you want to write this configuration to skaffold.yaml? [y/n]: y
16
Configuration skaffold.yaml was written
17
You can now run [skaffold build] to build the artifacts
18
or [skaffold run] to build and deploy
19
or [skaffold dev] to enter development mode, with auto-redeploy



Skaffold Continuous Development

We have set up all necessary configuration in order to experiment with the skaffold dev command. This will scan our project for any changes and automatically build and deploy them to our Kubernetes cluster. Run the following command:

Shell
 




xxxxxxxxxx
1


 
1
$ skaffold dev



Our application is being built and deployed to our Kubernetes cluster. We can also verify this with the minikube dashboard:

Shell
 




xxxxxxxxxx
1


 
1
$ minikube dashboard



We cannot invoke our URL yet because we did not create a service yet. We will map port 8080 via a NodePort. Generate the service yaml and add the contents (without the labels) to file service.yaml in the k8s directory:

Shell
 




xxxxxxxxxx
1
26


 
1
$ kubectl expose deployment myskaffoldplanet --type=NodePort --port=8080 --dry-run -oyaml
2
apiVersion: v1
3
kind: Service
4
metadata:
5
  creationTimestamp: null
6
  labels:
7
    app: myskaffoldplanet
8
    app.kubernetes.io/managed-by: skaffold-v1.1.0
9
    skaffold.dev/builder: local
10
    skaffold.dev/cleanup: "true"
11
    skaffold.dev/deployer: kubectl
12
    skaffold.dev/docker-api-version: "1.40"
13
    skaffold.dev/run-id: c8fc23d2-85f5-453a-bc22-19f4a9ec88a6
14
    skaffold.dev/tag-policy: git-commit
15
    skaffold.dev/tail: "true"
16
  name: myskaffoldplanet
17
spec:
18
  ports:
19
  - port: 8080
20
    protocol: TCP
21
    targetPort: 8080
22
  selector:
23
    app: myskaffoldplanet
24
  type: NodePort
25
status:
26
  loadBalancer: {}



Also, add the service.yaml as manifest file to the skaffold.yaml file:

YAML
 




xxxxxxxxxx
1


 
1
deploy:
2
 kubectl:
3
   manifests:
4
   - k8s/deployment.yaml
5
   - k8s/service.yaml



Skaffold immediately notices this change and creates the service. This can be verified in the Skaffold console output:

Shell
 




xxxxxxxxxx
1


 
1
Starting deploy...
2
- deployment.apps/myskaffoldplanet configured
3
- service/myskaffoldplanet created
4
Watching for changes...



Verify the creation of the service with kubectl:

Shell
 




xxxxxxxxxx
1


 
1
$ kubectl get services
2
NAME                 TYPE         CLUSTER-IP     EXTERNAL-IP    PORT(S)           AGE
3
kubernetes           ClusterIP    10.96.0.1                     443/TCP           24h
4
myskaffoldplanet     NodePort     10.96.65.87                   8080:30272/TCP    42s



The NodePort that has been assigned is port 30272 (see PORT(S) column). We are now able to invoke our Rest endpoint:

Shell
 




xxxxxxxxxx
1


 
1
$ curl $(minikube ip):30272/hello
2
Hello Skaffold! From host: myskaffoldplanet-76f44959c9-tcvw5/172.17.0.6



Change the greeting text in the HelloController:

Java
 




xxxxxxxxxx
1


1
StringBuilder message = new StringBuilder("Hello there, Skaffold!");



Again, the change is automatically detected by Skaffold, and in the background, our application is being built and deployed. Invoke the URL again:

Shell
 




xxxxxxxxxx
1


1
$ curl $(minikube ip):30272/hello
2
Hello there, Skaffold! From host: myskaffoldplanet-54b59fb785-hczn8/172.17.0.7



We can also use command skaffold run in order to deploy on request:

Shell
 




xxxxxxxxxx
1


 
1
$ skaffold run
2
...
3
Starting deploy...
4
- deployment.apps/myskaffoldplanet created
5
- service/myskaffoldplanet created
6
You can also run [skaffold run --tail] to get the logs



Again, check the NodePort the service is running and invoke the URL.

Troubleshooting

We once encountered the following error when running skaffold dev and skaffold run:

Shell
 




xxxxxxxxxx
1


 
1
rpc error: code = Unknown desc = Error response from daemon: pull access denied for mydeveloperplanet/myskaffoldplanet, repository does not exist or may require 'docker login': denied: requested access to the resource is denied



We solved this by adding the following lines to the skaffold.yaml file. Later on, it seemed not to be necessary anymore.

YAML
 




xxxxxxxxxx
1


1
build:
2
 local:
3
   push: false
4
 artifacts:
5
 - image: docker.io/mydeveloperplanet/myskaffoldplanet
6
   jib: {}



Conclusion

In this post, we looked at Skaffold for automatic building and deploying our application to a Kubernetes cluster during development. We only scratched the surface of what Skaffold is capable of, but we were very impressed by this tool. Definitely something to use and to keep an eye on.

Further Reading

50+ Useful Kubernetes Tools

 

 

 

 

Top