By Levent Ogut
Kubernetes brings some challenges to developers. They need to learn some new concepts, how they connect to each other, and most importantly, how to develop applications using a Kubernetes cluster. But the more time they spend in the weeds on these topics, the less time they have to build their applications. DevSpace is a Swiss-army knife for developers that closes this gap exceptionally well. DevSpace instructions are distributed as a simple YAML file in the repository to the whole team.
DevSpace is designed to ease the developers' lives by providing a wide range of tools. Various features of DevSpace allow the developer not to worry about the Kubernetes environment. Instead, it enables the developer to develop as they would do in their local machine.
Some features of DevSpace, including but are not limited to:
- Deployments via Helm, kubectl
- Dockerfile modification in-memory on execution time
- Development tools, such as file synchronization, log aggregation
- Custom hooks are actions that are carried out based on events.
- Custom commands which you can build complex or lengthy commands into a single sub-command.
- Custom profiles can be used to change anything in the
devspace.yaml
and Dockerfile using add, patch, remove, and merge operations. The profiles bring the ability to use different configurations for specific deployment types, e.g., staging, production, testing.
Requirements
Apart from access to a Kubernetes cluster -either remote or local- we need three CLI tools to be installed if it is not already.
You will also need a Flask-based Python project to continue; if you do not have a project, you can use the code presented here or refer to Flask documentation on how to create one.
Developing with DevSpace
DevSpace depends on its configuration file devspace.yaml
to perform all actions. Since we do not have one in place, let's ask DevSpace's help. Running devspace init
will analyze the current folder, ask some questions, and create a minimal devspace.yaml
file. This configuration file will describe all our deployments, dependencies, hooks, special commands, and so on.
$ devspace init
____ ____
| _ \ _____ __/ ___| _ __ __ _ ___ ___
| | | |/ _ \ \ / /\___ \| '_ \ / _` |/ __/ _ \
| |_| | __/\ V / ___) | |_) | (_| | (_| __/
|____/ \___| \_/ |____/| .__/ \__,_|\___\___|
|_|
? How do you want to deploy this project? helm: Use Component Helm Chart [QUICKSTART] (https://devspace.sh/component-chart/docs)
? How should DevSpace build the container image for this project? Create a new Dockerfile for this project
? Select the programming language of this project python
[info] DevSpace does *not* require pushing your images to a registry but let's assume you wanted to do that (optional)
? Which registry would you want to use to push images to? (optional, choose any) Use hub.docker.com => you are logged in as leventogut
[done] √ Great! You are authenticated with hub.docker.com
[info] Configuration saved in devspace.yaml - you can make adjustments as needed
[done] √ Project successfully initialized
You can now run:
- `devspace use namespace` to pick which Kubernetes namespace to work in
- `devspace dev` to start developing your project in Kubernetes
- `devspace deploy -p production` to deploy your project to Kubernetes
- `devspace -h` to get a list of available commands
Make sure the correct Kubernetes context is selected.
$ kubectl config use-context docker-desktop
Switched to context "docker-desktop".
Also, make sure the correct namespace is selected.
$ devspace use namespace default
We are going to use the following code for our app.
#!/usr/bin/env python
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello_world():
return "<p>Hello, World!</p>"
if __name__ == '__main__':
app.run(debug=True, use_debugger=True, use_reloader=False)
Save this code to a file called app.py in the project root directory, or use your own Flask project if you have one.
At this stage, I advise you to look at the devspace.yaml
, to get a feel of the generated configuration.
Since Flash uses port 5000 by default, we need to correct this in our devspace.yaml
in the dev.ports
section.
Now let's run devspace dev
.
$ devspace dev
[warn] Deploying into the 'default' namespace is usually not a good idea as this namespace cannot be deleted
[info] Using namespace 'default'
[info] Using kube context 'docker-desktop'
[info] Execute 'helm upgrade devspace-flask /Users/logut/.devspace/component-chart/component-chart-0.8.1.tgz --namespace default --values /var/folders/3h/tq577p717mdccgjpcgtcvqv80000gn/T/089143002 --install --kube-context docker-desktop'
[info] Execute 'helm list --namespace default --output json --kube-context docker-desktop'
[done] √ Deployed helm chart (Release revision: 2)
[done] √ Successfully deployed devspace-flask with helm
#########################################################
[info] DevSpace UI available at: http://localhost:8090
#########################################################
[done] √ Port forwarding started on 5000:5000 (default/devspace-flask-5f785f7fdd-6rx5f-devspace)
[0:sync] Waiting for pods...
[0:sync] Starting sync...
[0:sync] Sync started on /Users/logut/dev/loft/devspace-flask <-> . (Pod: default/devspace-flask-5f785f7fdd-6rx5f-devspace)
[0:sync] Waiting for initial sync to complete
[info] Opening 'http://localhost:8080' as soon as application will be started (timeout: 4m0s)
[info] Opening shell to pod:container devspace-flask-5f785f7fdd-6rx5f-devspace:container-0
Installing Python Dependencies
Requirement already satisfied: click==8.0.1 in /usr/local/lib/python3.9/site-packages (from -r requirements.txt (line 1)) (8.0.1)
Requirement already satisfied: Flask==2.0.1 in /usr/local/lib/python3.9/site-packages (from -r requirements.txt (line 2)) (2.0.1)
Requirement already satisfied: itsdangerous==2.0.1 in /usr/local/lib/python3.9/site-packages (from -r requirements.txt (line 3)) (2.0.1)
Requirement already satisfied: Jinja2==3.0.1 in /usr/local/lib/python3.9/site-packages (from -r requirements.txt (line 4)) (3.0.1)
Requirement already satisfied: MarkupSafe==2.0.1 in /usr/local/lib/python3.9/site-packages (from -r requirements.txt (line 5)) (2.0.1)
Requirement already satisfied: Werkzeug==2.0.1 in /usr/local/lib/python3.9/site-packages (from -r requirements.txt (line 6)) (2.0.1)
WARNING: Running pip as root will break packages and permissions. You should install packages reliably by using venv: https://pip.pypa.io/warnings/venv
WARNING: You are using pip version 21.1.2; however, version 21.2.4 is available.
You should consider upgrading via the '/usr/local/bin/python -m pip install --upgrade pip' command.
____ ____
| _ \ _____ __/ ___| _ __ __ _ ___ ___
| | | |/ _ \ \ / /\___ \| '_ \ / _` |/ __/ _ \
| |_| | __/\ V / ___) | |_) | (_| | (_| __/
|____/ \___| \_/ |____/| .__/ \__,_|\___\___|
|_|
Welcome to your development container!
This is how you can work with it:
- Run `python main.py` to build the application
- Files will be synchronized between your local machine and this container
- Some ports will be forwarded, so you can access this container on your local machine via localhost:
Image ImageSelector LabelSelector Ports (Local:Remote)
imagerepo-repo1/app 5000:5000
root@devspace-flask-7c4bd546-nfmzf-devspace:/app#
Let's run our application.
root@devspace-flask-7c4bd546-nfmzf-devspace:/app# python app.py
* Serving Flask app 'app' (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 276-822-672
We have selected the debug option so that the Flask development server will reload once there is a change in the code files.
devspace dev
command performed several actions to set up our development environment.
- Using Helm, it deployed our application to the Kubernetes cluster by using a component chart.
- Started port forwarding on port 5000 so that we can access the application as it is running locally.
- Set up the synchronization of the local folder into the container using the sync feature.
- Parsed the
requirement.txt
file and install all dependencies. - Using the
dev.replacedPods
feature, it replaced the original container's image with a development one. - Opened a shell into the application container. Note that this is done by changing the
CMD
instruction in the Dockerfile, so you need to start the application yourself, as shown above.
By default, DevSpace uses dev.replacedPods
for development. The ability to replace the image allows the developer to use a different image with the same code base. A developer can create an image with debugging tools installed to be used in the development process.
Now you can exit the container shell and terminate DevSpace. The following section will add caching functionality so that we can see the changes we need to do in our devspace.yaml
.
Adding Cache (Memcached)
Let's add a caching solution to our Flask application. This will allow us to configure devspace.yaml
. Following is the snippet we need to add to the deployments section of the devspace.yaml
. This snippet defines a deployment using helm with Bitnami repository and Memcached
chart. We also specify the chart version.
- name: memcached
helm:
componentChart: false
chart:
name: memcached
version: 5.12.0
repo: https://charts.bitnami.com/bitnami
And modify the vars
section, as follows:
vars:
- name: CACHE_MEMCACHED_SERVERS
Adding Environment Values to a Deployment
To add the environment value to our app deployment pod, we need to define them in the deployments section; you only need the env section to be added to the existing devspace.yaml
.
- name: devspace-flask
# This deployment uses `helm` but you can also define `kubectl` deployments or kustomizations
helm:
# We are deploying the so-called Component Chart: https://devspace.sh/component-chart/docs
componentChart: true
# Under `values` we can define the values for this Helm chart used during `helm install/upgrade`
# You may also use `valuesFiles` to load values from files, e.g. valuesFiles: ["values.yaml"]
values:
containers:
- image: ${IMAGE} # Use the value of our `${IMAGE}` variable here (see vars above)
env:
- name: CACHE_MEMCACHED_SERVERS
value: $!{CACHE_MEMCACHED_SERVERS}
service:
ports:
- port: 5000
As all our configuration is ready, we can now fill in the environment value of the cache servers. Run the following command.
$ devspace list vars
DevSpace will ask you if it can't find a value for the environment variable. Variables in DevSpace can be sourced from environment, command, input, and hard-coded values. These variables can be used in any part of the configuration.
? Please enter a value for CACHE_MEMCACHED_SERVERS memcached:11211
Variable Value
CACHE_MEMCACHED_SERVERS memcached:11211
IMAGE imagerepo-repo1/app
Next, we need to configure our application to use caching.
Flask Cache Settings
Following is the change we made to enable caching on the /
path. First, we import a caching module, also configuring this module for cache servers; as you noted here, we are using an environment variable, the one that we have given in the sections before; next, configure the application for the cache; lastly, we add cache decorator to our route function.
diff --git a/app.py b/app.py
index d26f024..f840dc3 100755
--- a/app.py
+++ b/app.py
@@ -1,11 +1,16 @@
#!/usr/bin/env python
from flask import Flask
+from flask_caching import Cache
+import os
-app = Flask(__name__)
+cache = Cache(config={'CACHE_TYPE': 'MemcachedCache','CACHE_MEMCACHED_SERVERS': [os.environ['CACHE_MEMCACHED_SERVERS']]})
+app = Flask(__name__)
+cache.init_app(app)
@app.route("/")
+@cache.cached(timeout=50)
def hello_world():
return "<p>Hello, World!</p>"
if __name__ == '__main__':
app.run(debug=True, use_debugger=True, use_reloader=False)
Here is the copy-paste-friendly version.
#!/usr/bin/env python
from flask import Flask
from flask_caching import Cache
import os
cache = Cache(config={'CACHE_TYPE': 'MemcachedCache','CACHE_MEMCACHED_SERVERS': [os.environ['CACHE_MEMCACHED_SERVERS']]})
app = Flask(__name__)
cache.init_app(app)
@app.route("/")
@cache.cached(timeout=50)
def hello_world():
return "<p>Hello, World!</p>"
if __name__ == '__main__':
app.run(debug=True, use_debugger=True, use_reloader=False)
Now re-run devspace dev
to start our development environment.
Once you are in the shell, run python app.py
command to start a development server.
root@devspace-flask-7c4bd546-nfmzf-devspace:/app# python app.py
* Serving Flask app 'app' (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 276-822-672
Now visiting the /
endpoint will create cache entries.
root@devspace-flask-7c4bd546-nfmzf-devspace:/app# curl http://127.0.01:5000
<p>Hello, World!</p>
We can also see the requests on the logs.
127.0.0.1 - - [21/Sep/2021 11:10:13] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [21/Sep/2021 11:10:15] "GET / HTTP/1.1" 200 -
Checking Cache Stats
To ensure our cache is working as expected, let's telnet to it from the app container and list stats. You can install telnet with the apt install -y telnet
command.
root@devspace-flask-5f785f7fdd-6rx5f-devspace:/app# telnet memcached 11211
Trying 10.108.20.56...
Connected to memcached.default.svc.cluster.local.
Escape character is '^]'.
stats items
STAT items:2:number 1
STAT items:2:number_hot 0
STAT items:2:number_warm 0
STAT items:2:number_cold 1
STAT items:2:age_hot 0
STAT items:2:age_warm 0
STAT items:2:age 26
STAT items:2:mem_requested 101
STAT items:2:evicted 0
STAT items:2:evicted_nonzero 0
STAT items:2:evicted_time 0
STAT items:2:outofmemory 0
STAT items:2:tailrepairs 0
STAT items:2:reclaimed 1
STAT items:2:expired_unfetched 0
STAT items:2:evicted_unfetched 0
STAT items:2:evicted_active 0
STAT items:2:crawler_reclaimed 0
STAT items:2:crawler_items_checked 0
STAT items:2:lrutail_reflocked 0
STAT items:2:moves_to_cold 3
STAT items:2:moves_to_warm 2
STAT items:2:moves_within_lru 1
STAT items:2:direct_reclaims 0
STAT items:2:hits_to_hot 2
STAT items:2:hits_to_warm 1
STAT items:2:hits_to_cold 4
STAT items:2:hits_to_temp 0
END
We can see that responses have already been cached.
We have deployed our application in development mode, we have added a cache into the mix, now let's see DevSpace's sync feature. This will enable us to continue to develop locally and see the results in the Kubernetes cluster.
Create a New Endpoint
First, send a couple of requests to the /ping
endpoint, validate the response is 404; then edit the app.py
file and add the following function with route decorator:
@app.route("/ping")
def ping():
return "pong"
With this small code, we have created an endpoint that returns pong
responses to all requests. Now send some requests again, and the response will be 200 with the content of pong
.
Following is the output of the steps we have done above:
root@devspace-flask-7c4bd546-nfmzf-devspace:/app# python app.py
* Serving Flask app 'app' (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 276-822-672
127.0.0.1 - - [21/Sep/2021 11:29:44] "GET /ping HTTP/1.1" 404 -
127.0.0.1 - - [21/Sep/2021 11:29:49] "GET /ping HTTP/1.1" 404 -
* Detected change in '/app/app.py', reloading
* Restarting with stat
* Debugger is active!
* Debugger PIN: 276-822-672
127.0.0.1 - - [21/Sep/2021 11:29:50] "GET /ping HTTP/1.1" 200 -
127.0.0.1 - - [21/Sep/2021 11:29:51] "GET /ping HTTP/1.1" 200 -
As you can see, before we added the code, the endpoint was giving a 404 not found error; right after we added the code, we can see 200 return codes and text response of pong
. An important aspect of this setup is that we are using debug=True
. This makes the server watch the local Python files and reload when there is a change. So, in summary, DevSpace detects the change in your local machine, syncs the change to the container, debug server detects the change, and reloads the code. This operation allows us to develop locally and immediately deploy to the Kubernetes cluster.
Deploying to Production
DevSpace is not only a development tool. It can be used to deploy to staging, production, and even with CI/CD pipelines. With the Profiles feature, you can create different deployment settings for your application and its dependencies. Changes include Dockerfile
changes, devspace.yaml
changes, and so on. You can find a production
profile in your initial devspace.yaml
You can select a profile or multiple profiles you want to use as follows:
$ devspace deploy -p production
Troubleshooting with Devspace
Logs
By default, DevSpace drops you into the shell of the application container so you can run any command and troubleshoot errors. You can also change this behavior so that logs from all or some (filtering) containers will be streamed.
For now, open another terminal and run the following command to see any container logs.
$ devspace logs
When you don't specify the container, DevSpace will ask you which container you would like to get the logs of.
? Select a container memcached-6c775fbd9d-hwbhf:memcached
[info] Printing logs of pod:container memcached-6c775fbd9d-hwbhf:memcached
memcached 14:11:52.97
memcached 14:11:52.97 Welcome to the Bitnami memcached container
memcached 14:11:52.97 Subscribe to project updates by watching https://github.com/bitnami/bitnami-docker-memcached
memcached 14:11:52.98 Submit issues and feature requests at https://github.com/bitnami/bitnami-docker-memcached/issues
memcached 14:11:52.98
memcached 14:11:52.98 INFO ==> ** Starting Memcached setup **
memcached 14:11:52.99 INFO ==> Initializing Memcached
memcached 14:11:53.00 INFO ==> ** Memcached setup finished! **
memcached 14:11:53.00 INFO ==> ** Starting Memcached **
Entering into Containers
You can use DevSpace to control the deployed environment. One aspect is quickly entering the containers. Not specifying the container name allows you to select from a list.
$ devspace enter
? Which pod do you want to open the terminal for? [Use arrows to move, type to filter]
> devspace-flask-7c4bd546-nfmzf-devspace:container-0
memcached-6c775fbd9d-hwbhf:memcached
We also can specify the container name directly with the -c
flag. As you can see, this is much easier than getting the random pod name and then also specifying the container name; DevSpace does this automatically for the resource it has deployed.
$ devspace enter -c container-0
[info] Opening shell to pod:container devspace-flask-7c4bd546-nfmzf-devspace:container-0
root@devspace-flask-7c4bd546-nfmzf-devspace:/app#
Running Commands within a Container
Using the same subcommand enter, we can run commands within the container directly.
$ devspace enter -c container-0 -- cat requirements.txt
[info] Opening shell to pod:container devspace-flask-7c4bd546-nfmzf-devspace:container-0
click==8.0.1
Flask==2.0.1
Flask-Caching==1.10.1
itsdangerous==2.0.1
Jinja2==3.0.1
MarkupSafe==2.0.1
python-memcached==1.59
six==1.16.0
Werkzeug==2.0.1
$ devspace enter -c memcached -- ps aux
[info] Opening shell to pod:container memcached-6c775fbd9d-hwbhf:memcached
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
1001 1 0.0 0.0 409280 3684 ? Ssl Sep20 0:44 memcached -u me
1001 58 0.0 0.0 7644 2700 pts/0 Rs+ 12:40 0:00 ps aux
DevSpace also has a Commands feature where you can create custom commands to run locally or in a specific container; this is especially useful for lengthy and error-prone commands you want to run.
Clean Up
DevSpace provides a subcommand that deletes all the deployments and the resources (except PV/PVC) easily so you can remove the development environment with ease.
$ devspace purge
Conclusion
We have seen and implemented some features of DevSpace that will help developers. Seamless deployment, ease of development are powerful features. Also, being able to distribute this configuration to the team is very easy to do.
There are many features of DevSpace that we haven't had time to cover. To learn more, have a look at the documentation or reach us on the Kubernetes Slack in the #devspace channel.
Top comments (0)