PHP Laravel Development with Kubernetes using DevSpace - Developer Edition

Kubernetes is an excellent open-source container orchestration platform that brings automatic scaling, automatic recovery, observability, and many more features. Since it differs from traditional operations, it has changed the development and deployment workflows as well. Debugging an application on Kubernetes can be a challenge. DevSpace is a tool that helps you develop, deploy, troubleshoot simple or complex applications. We will use a Laravel project to demonstrate its features; Laravel is a popular framework used by the PHP community with great features like extensibility, inheritance, and reusability with high customization options.

#Architecture

We will look at ways to deploy a Laravel based application into a Kubernetes cluster for development and production environments. We will develop our application while the application is running in Kubernetes as if we are developing locally. And we will be able to troubleshoot our application in real-time with ease.

The desired setup uses four containers, in three pods:

  • PHP-FPM container, which processes all the PHP assets.
  • Nginx container, which serves static files and acts as a reverse proxy for the PHP assets.
  • MySQL container, as the database.
  • Redis container, as session and cache.

#Introduction to DevSpace

Continuous Delivery is a challenge while developing on Kubernetes. Without using a special tool, you need to build and deploy every time code or assets change. DevSpace handles this seamlessly either by synchronizing files and hot reload of the container in question or automatically rebuilding and deploying the image(s) required. DevSpace allows you to develop in a Kubernetes cluster as if you are developing in your local machine.

#Feature Highlights

  • Agile development on local and remote Kubernetes clusters. Execution of entire continuous development and deployment pipeline, and a single command to deploy all components of your application.
  • Declarative configuration kept in source code, in the devspace.yaml file. All of the development, deployment, and pre/post-deployment actions are defined in a single file.
  • Hot Reloading for faster feedback. Instead of building and re-deploying artifacts, DevSpace allows you to use high-performance and bi-directional file synchronization. This allows changes to trigger a hot-reload on the deployed container. All of these features are highly configurable.
  • Extensibility. You can extend the functionality of DevSpace via the plugin system. Hooks and commands are also built-in constructs; you can expand the functionality heavily used in CI/CD pipelines.
  • Easy clean Up. You can delete the resources created via `devspace purge` in a single simple step.
  • Client Only. DevSpace doesn’t require server/cluster side components. A single executable on a local machine is sufficient to develop, troubleshoot and deploy.

#Requirements and Setting Up Development Environment

The following tools should be installed on your local development machine:

  • Kubectl, documentation here.
  • Helm, documentation here.
  • DevSpace, installation instructions here.

#Developing with DevSpace

First, let’s start with the code. Clone the repository to your local development machine as follows. This code includes a vanilla Laravel installation, a Dockerfile, and a devspace.yaml prepopulated.

$ git clone git@github.com:loft-sh/devspace-php-laravel-nginx.git

Copy .env.example to .env.

$ cp .env.example .env

Now open the .env file and modify where necessary, like adjusting port numbers if needed.

After this, we can generate the Laravel APP_KEY variable via the following command:

$ devspace run generate-key

Review the variables by running the devspace list vars command, and set variables where necessary.

$ devspace list vars

DevSpace will ask you a few questions regarding the image repository and other variables not defined in the .env file, and then show the output of the variables.

 Variable              Value                                                
 APP_DEBUG             true                                                 
 APP_IMAGE             leventogut/php-laravel-nginx-devspace                
 APP_KEY               xxxxxxxxxxxxxxxx  
 ASSET_VOLUME_NAME     static-asset-volume                                  
 ASSET_VOLUME_SIZE     1Gi                                                  
 DB_DATABASE           laravel                                              
 DB_HOST               mysql                                                
 DB_MYSQL_VERSION      8.0.23                                               
 DB_PASSWORD           xxxxxxxxxxxxxxxx                                     
 DB_PORT               3306                                                 
 DB_ROOT_PASSWORD      xxxxxxxxxxxxxxxx                                     
 DB_USERNAME           laravel                                              
 NGINX_CONFIG_HASH     740941                                               
 NGINX_IMAGE_VERSION   1.9                                                  
 REDIS_PASSWORD        xxxxxxxxxxxxxxxx                                    
 REDIS_VERSION         6.0.12  

#Running devspace dev

DevSpace is context-aware; it follows your Kubernetes config to determine the Kubernetes cluster to deploy on.  However, It is good practice to set the context and namespace to use with:

$ devspace use context docker-desktop
[info]   Your kube-context has been updated to 'docker-desktop'
         To revert this operation, run: devspace use context maple-staging

[done] √ Successfully set kube-context to 'docker-desktop'
$ devspace use namespace laravel
[info]   The default namespace of your current kube-context 'docker-desktop' has been updated to 'laravel'
         To revert this operation, run: devspace use namespace 

[done] √ Successfully set default namespace to 'laravel'

Now that we have all the variables and the configs, we can start in-cluster development:

$ devspace dev

DevSpace will build the artifacts we have defined in the devspace.yaml, deploy all components, and start log streaming from the configured containers. This might take a few minutes.

In a few minutes, DevSpace will open a browser window showing a login screen. Previously, we have installed the laravel/ui package to test MySQL and Redis. Simply register as a new user, and you will be redirected to the index page. The index page has several links, including a link to the ping/pong route we will use in a few steps.

Screenshot of the PHP app's UI

#Workflow

At this stage, we have deployed our application into the Kubernetes cluster, and DevSpace is watching any changes on the project directory.

Now, having started DevSpace in development mode, we can change our code and see the immediate effect on our application that is running in the Kubernetes cluster.

Open the web.php file under the routes directory with your favorite editor. And paste the following code.

Route::get('ping', function () {
    return "pong";
})

Any asset added to the repo folder will also be synced (according to the sync rules defined) to the container.

At this stage, you can try adding controllers, routes, dependencies and observe the ease of development.

#Port Forwarding and Reverse Port Forwarding

You can reach the application via port forwarding. These can be defined in the devspace.yaml file. In the current configuration, the Nginx container’s port 80 is forwarded to local port 8080. The browser will be automatically opened after a successful deployment and start of the containers.

You can configure reverse port forwarding as well, which is very useful for certain debugging tools.

#Commands

Our sample devspace.yaml includes some Laravel and MySQL-specific commands to ease development workflow.

You can run any artisan, composer, php, and npm commands and additionally drop into a MySQL shell with a single mysql command.

You can list available commands via:

$ devspace list commands
 Name           Command                                                            Description
 artisan        devspace enter -c php -- php artisan                               Entry point for artisan commands.
 composer       devspace enter -c php -- composer                                  Entry point for composer commands.
 php            devspace enter -c php -- php                                       Entry point for PHP commands.
 npm            devspace enter -c php -- npm                                       Entry point for NPM commands.
 generate-key   TMP_FILE=.devspace/app_key.tmp && docker run --rm -v $PWD:/ap...   Generate APP_KEY.
 mysql          devspace enter -c mysql -- mysql -h'mysql' -P'3306' -u'larave...   Enter to MySQL shell.

Try these commands to get familiar with them in your workflow.

#Hooks

Hooks are a quite valuable feature of DevSpace. With hooks, you can run commands before and after certain deployments.

We have defined several hooks in the devspace.yaml file, such as:

  • Changing MySQL user and password
  • Running npm run watch on the PHP container.
  • Reloading Nginx to re-read the configuration.

#Deploying to Production

The devspace deploy command will deploy our application into the environment we define. DevSpace configuration allows us to modify and alter our parameters based on profiles. This flexibility brings numerous configuration options for development, staging, production environments. DevSpace configuration can hold all different parameters and configurations for each environment. Generally speaking, it is a good practice to create a production profile for deployment, which will remove troubleshooting aids and set parameters accordingly.

Our prepared devspace.yaml consists of a production profile that will remove the additions we have made to make developing and troubleshooting easy.

Deploy with production profile:

$ devspace deploy -p production

#Troubleshooting with DevSpace

Troubleshooting and debugging are pretty straightforward with DevSpace. DevSpace provides aid for the following:

  • Logging
  • Entering into containers
  • Running commands inside the containers
  • Interactive mode

#Entering to and Working with Containers

The devspace enter command allows you to open a shell to any of the running containers by providing the container name, so you don’t have to deal with the copy/paste of long pod names.

$ devspace enter -c php
[info]   Using namespace 'default'
[info]   Using kube context 'docker-desktop'
[info]   Opening shell to pod:container app-0:php
root@app-0:/var/www/html# 

If a container is not specified, a selector will be displayed, and you can choose from the available containers:

? Which pod do you want to open the terminal for?
  [Use arrows to move, type to filter]
> redis-master-0:redis  app-0:nginx
  app-0:php
  mysql-0:mysql

#Testing

As you can execute any command in the container, running the tests you have for the application is a breeze.

You can easily run phpunit or artisan test commands for running your tests.

$ devspace run php ./vendor/bin/phpunit
[info]   Using namespace 'default'
[info]   Using kube context 'docker-desktop'
[info]   Opening shell to pod:container app-0:php
PHPUnit 9.5.3 by Sebastian Bergmann and contributors.

.F                                                                  2 / 2 (100%)

Time: 00:00.122, Memory: 20.00 MB

There was 1 failure:

1) Tests\Feature\ExampleTest::testBasicTest
Expected status code 200 but received 302.
Failed asserting that 200 is identical to 302.

/var/www/html/vendor/laravel/framework/src/Illuminate/Testing/TestResponse.php:187
/var/www/html/tests/Feature/ExampleTest.php:19

FAILURES!
Tests: 2, Assertions: 2, Failures: 1.

Let’s run artisan test:

$ devspace run artisan test
[info]   Using namespace 'default'
[info]   Using kube context 'docker-desktop'
[info]   Opening shell to pod:container app-0:php

   PASS  Tests\Unit\ExampleTest
  ✓ basic test

   FAIL  Tests\Feature\ExampleTest
  ⨯ basic test

  ---

  • Tests\Feature\ExampleTest > basic test
  Expected status code 200 but received 302.
  Failed asserting that 200 is identical to 302.

  at tests/Feature/ExampleTest.php:19
     15▕     public function testBasicTest()
     16▕     {
     17▕         $response = $this->get('/');
     18▕ 
  ➜  19▕         $response->assertStatus(200);
     20▕     }
     21▕ }
     22▕ 


  Tests:  1 failed, 1 passed
  Time:   0.19s

#CI/CD

DevSpace configuration can hold many profiles and can be used for different deployment options. It is common to see developers use DevSpace for their CI/CD pipeline as well. Deploying your application into the CI/CD pipeline is relatively straightforward. The ability to choose from various profiles makes it a breeze to switch from development to staging and production.

#Clean Up

You can easily clean your environment with the `devspace purge` command. This will be deleting all deployments. Please note that purge will not delete persistent storage(s).

#Conclusion

We have seen DevSpace in action while developing, deploying, and troubleshooting. We have seen that once DevSpace is configured, it can encompass all deployment options within it. So it can be used for development and deployment to any environment. The ability to change profiles, add new commands, and execute any hooks is advantageous.

The second part of this series will delve into how to configure DevSpace, and we will go over the many possible configuration options.

#Further Reading

Photo by Ben on Unsplash