DEV Community

Cover image for Kubernetes-native development: sync files with Ksync
Dmitry Salahutdinov
Dmitry Salahutdinov

Posted on

Kubernetes-native development: sync files with Ksync

Previous post I started to set up continuous deployment for the development process. Skaffold is a handy tool for doing that.

Let's go deeper this time and imagine the next steps for making up a full-featured "local" development environment with Kubernetes. Running ruby code outside of the local machine keeps it clear and doesn't impose any resource limits.

It is the essence of the idea "Kubernetes-native development": to run all the code at a remote or local Kubernetes cluster.

The issue I try to investigate here is file synchronization. If we edit code locally and run it somewhere remotely, we could get sources out of sync. Unequal sources could cause unexpected results.

For local development with Docker it is common to use Docker volumes. The same idea could be applied here in terms of Kubernetes Persistent Volumes. I failed to use persistent volume, mapping them to the Minikube host machine. And map to local Mac directory onto the Minikube host machine.
Even if it worked well, there are other issues with that approach:

  • a cluster could be remote. It's a big deal to map the local directory into the remote cluster host machine
  • having additional persistent volume manifests only for developer's needs is obsessive

I decided to synchronize files instead and try out the ksync tool for Kubernetes.

Ksync speeds up developers who build applications for Kubernetes. It transparently updates containers running on the cluster from your local checkout. This enables developers to use their favorite IDEs, such as Atom or Sublime Text to work from inside a cluster instead of from outside it. There is no reason to wait minutes to test code changes when you can see the results in seconds.

If want to do something like docker run -v /foo:/bar with Kubernetes, ksync is for you!

Let's set up file sync for already known Skaffold Ruby example. We'll need the same tooling as in the previous article: kubectl, minikube, skaffold, and installed ksync.

Install ksync

Find the installation details here, or just run for MacOS:

$ curl https://ksync.github.io/gimme-that/gimme.sh | bash
Enter fullscreen mode Exit fullscreen mode

Setup ksync

Then we have to initialize ksync.

$ ksync init
Enter fullscreen mode Exit fullscreen mode

This command initializes ksync and installs the server component on your cluster. One part of ksync works as the entity in the Kubernetes cluster and has to be set up. See more details about ksync architecture.

Check out the Ruby example sources:

$ git clone git@github.com:GoogleContainerTools/skaffold.git
$ cd examples/ruby
Enter fullscreen mode Exit fullscreen mode

We have to enable ksync file watch with running the watch command:

$ ksync watch
Enter fullscreen mode Exit fullscreen mode

Enable watching changes within ($pwd)/backend folder and sync them back and forth with /app/ directory of running containers:

We will use the specific label app.kubernetes.io/ksync: 'true' presence to determine the scope of pod container to track changes with:

$ ksync create --selector app.kubernetes.io/ksync=true --reload=false -n default $(pwd)/backend /app/
Enter fullscreen mode Exit fullscreen mode

Ruby app sample

Change the k8s/deployment.yaml to add the specific tag app.kubernetes.io/ksync: 'true' to pod's containers.

apiVersion: v1
kind: Service
metadata:
  name: ruby
spec:
  ports:
  - port: 9292
    targetPort: 9292
  type: LoadBalancer
  selector:
    app: ruby
--------
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ruby
spec:
  selector:
    matchLabels:
      app: ruby
  template:
    metadata:
      labels:
        app: ruby
        app.kubernetes.io/ksync: 'true'
    spec:
      containers:
      - name: ruby
        image: ruby-example
        ports:
        - containerPort: 9292
        env:
          - name: RACK_ENV
            value: "development"
Enter fullscreen mode Exit fullscreen mode

And deploy the application:

$ skaffold run --tail
Generating tags...
 - ruby-example -> ruby-example:v1.1.0-118-g92f37b200-dirty
Checking cache...
 - ruby-example: Found Locally
Tags used in deployment:
 - ruby-example -> ruby-example:de8f4f77b01e508f5d862a9ef84d9f33a50d03a88225b0e1b26ddb04d948cded
   local images can't be referenced by digest. They are tagged and referenced by a unique ID instead
Starting deploy...
 - service/ruby created
 - deployment.apps/ruby created
[ruby-66d8d5758-g6m9r ruby] Puma starting in single mode...
[ruby-66d8d5758-g6m9r ruby] * Version 4.3.1 (ruby 2.7.0-p0), codename: Mysterious Traveller
[ruby-66d8d5758-g6m9r ruby] * Min threads: 0, max threads: 16
[ruby-66d8d5758-g6m9r ruby] * Environment: development
[ruby-66d8d5758-g6m9r ruby] * Listening on tcp://0.0.0.0:9292
[ruby-66d8d5758-g6m9r ruby] Use Ctrl-C to stop
Enter fullscreen mode Exit fullscreen mode

This time we won't run skaffold in development mode to prevent the doubled file sync by ksync and skaffold concurrently.

File sync in action

Any changes made on local files are automatically synchronized with remote containers:

$ echo "gem 'oj'" >> backend/Gemfile
Enter fullscreen mode Exit fullscreen mode

The similar ksync watch output means that file is synchronized:

INFO[0008] folder sync running                           pod=ruby-66d8d5758-g6m9r spec=massive-koala
INFO[0012] updating                                      pod=ruby-66d8d5758-g6m9r spec=massive-koala
INFO[0012] update complete                               pod=ruby-66d8d5758-g6m9r spec=massive-koala
INFO[0012] updating                                      pod=ruby-console-69674c499f-97m79 spec=massive-koala
INFO[0012] update complete                               pod=ruby-console-69674c499f-97m79 spec=massive-koala
INFO[0080] updating                                      pod=ruby-console-69674c499f-97m79 spec=massive-koala
INFO[0080] updating                                      pod=ruby-66d8d5758-g6m9r spec=massive-koala
INFO[0080] update complete                               pod=ruby-66d8d5758-g6m9r spec=massive-koala
INFO[0080] update complete                               pod=ruby-console-69674c499f-97m79 spec=massive-koala
Enter fullscreen mode Exit fullscreen mode

Let check the container's Gemfile to see if changes are applied:

$ kubectl exec -it $(kubectl get pods --selector app.kubernetes.io/ksync=true | tail -n 1 | awk '{print $1}') --  cat Gemfile
source 'https://rubygems.org'

gem 'rack'
gem 'rack-unreloader' # for dynamic reloading
gem 'puma'
gem 'oj'
Enter fullscreen mode Exit fullscreen mode

Then we could get inside the pod and run bundle to update Gemfile.lock dependencies lock-file:

$ kubectl exec -it $(kubectl get pods --selector app.kubernetes.io/ksync=true | tail -n 1 | awk '{print $1}') -- bundle

Using bundler 2.1.2
Using nio4r 2.5.2
Using oj 3.10.1
Using puma 4.3.1
Using rack 2.1.1
Using rack-unreloader 1.7.0
Bundle complete! 4 Gemfile dependencies, 6 gems now installed.
Enter fullscreen mode Exit fullscreen mode

After bundle had finished and updated the lock-file, we got it synchronized back with a local machine!

Conclusion

Perfect, we set up the two-way file synchronization and get it working with ksync. From now, any source file updates made automatically by ruby-code, get back to a local machine.

The sync would also work well for generated files, such as rails g migration or even for initial rails project generation with rails new my_project.

Top comments (0)