Kubernetes (K8s) / Minikube — Develop Locally Like A Pro With PHP & Telepresence

Looking for a way to develop and interact with your Kubernetes (K8s) cluster locally but without the need to constantly re-build Docker contains?

Then read on my friend.

TL;DR

We setup 2 Laravel projects, have one of them required a JSON response via Guzzle (HTTP) from the other project then remove one project with a local version, updating it with new changes and viewing the results in real-time without running any Docker build commands.

Laying The Ground Work (Installations)

  1. Install & run Minikube (Don’t have Minikube? Get it here)
  2. Install Telepresence (Get it from here)
  3. Install the Laravel Installer (Get it from here)

Setup The Testing Projects

Let’s install 2 Laravel projects to work with in Minikube. Follow the commands below and adjust as required.

Project 1

New Project Folder

mkdir laravel-test-1

Navigate to project

cd laravel-test-1

New Installation

laravel new site

Navigate to site

cd site

Install dependencies

composer install

Project 2

New Project Folder

mkdir laravel-test-2

Navigate to project

cd laravel-test-2

New Installation

laravel new site

Navigate to site

cd site

Install dependencies

composer install

For each project add the following files:

deployment.yaml

Note: Replace anywhere it says laravel-test-1 with laravel-test-2 for the second project

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
run: laravel-test-1
name: laravel-test-1
namespace: default
spec:
replicas: 1
selector:
matchLabels:
run: laravel-test-1
template:
metadata:
labels:
run: laravel-test-1
spec:
containers:
- name: laravel-test-1
env:
- name: APP_KEY
valueFrom:
configMapKeyRef:
name: laravel-test-1-config
key: APP_KEY
name: APP_NAME
valueFrom:
configMapKeyRef:
name: laravel-test-1-config
key: APP_NAME
image: laravel-test-1:0.0.1
imagePullPolicy: Never
ports:
- containerPort: 80
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
labels:
run: laravel-test-1
name: laravel-test-1
namespace: default
spec:
externalTrafficPolicy: Cluster
ports:
- nodePort: 31010
port: 80
protocol: TCP
targetPort: 80
selector:
run: laravel-test-1
sessionAffinity: None
type: NodePort
---
apiVersion: v1
kind: ConfigMap
metadata:
name: laravel-test-1-config
namespace: default
data:
APP_NAME: "Laravel Test 1"
APP_KEY: "base64:kd+ghk4E5wrhPLpQLzVGD4BI9jVd4pl2sP6jRD4Z6bc="

vhost.conf

<VirtualHost *:80>
DocumentRoot /app/public
<Directory "/app/public">
AllowOverride all
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

Dockerfile

Note: This file has no extension, it’s just saved exactly how it’s written — Dockerfile

FROM composer:1.6.5 as build
WORKDIR /app
COPY ./site /app
RUN composer install
FROM php:7.1.8-apache
EXPOSE 80
COPY --from=build /app /app
COPY vhost.conf /etc/apache2/sites-available/000-default.conf
RUN chown -R www-data:www-data /app \
&& a2enmod rewrite

.env

Note: Just an example, stripped down if required

APP_ENV=local
APP_DEBUG=true
APP_URL=http://laravel-test-1
LOG_CHANNEL=stack
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

Update laravel-test-1 routes

By default, the Laravel project will have a file called routes/web.php, edit this file by replacing the existing / route with this:

Route::get('/', function () {
$client = new GuzzleHttp\Client();
$res = $client->request('GET', 'http://laravel-test-2:8080/api/payload');
$body = json_decode($res->getBody()->getContents());
    return response()->json([
'status' => $res->getStatusCode(),
'content-type' => $res->getHeader('content-type')[0],
'response' => collect($body),
], 200);
});

Update laravel-test-2 routes

Now update the other Laravel project with a matching /api/payload route, to simulate a microservice type environment over HTTP. routes/api.php

Route::get('payload', function () {
return response()->json([
'message' => 'Sending back the payload!',
'users' => ['mick', 'nick', 'rick', 'pickle'],
'companies' => ['apple', 'dell', 'microsoft'],
], 200);
});

This setup will help you simulate the right environment and also allow us to continue to update the payload in real-time when we are finished.

Start It Up

Let’s launch everything to get a nice base foundation happening. This will create a new deployment per project which will allow for the pods to interact with other pods (Cluster IP), to be able to access the project from your web browser and develop locally using Telepresence.

Navigate to the first project

cd laravel-test-1

Build the Docker container (this will be stored locally, within the Minikube environment so you don’t need to upload and download containers)

eval $(minikube docker-env)

Try and use a simple namespace for your Docker containers.

docker build -t 5150studios/laravel-test-1 .

Launch into Minikube / Kubernetes

kubectl apply -f .

Test the project out in your browser, you should get the default Laravel welcome page.

minikube service --url laravel-test-1
http://192.168.99.103:31010

Next step is to repeat this process but for laravel-test-2

By hitting the base url on laravel-test-1, you should see the following json response:

// 20190211151359
// http://192.168.99.103:31010/
{
"status": 200,
"content-type": "application/json",
"response": {
"message": "Sending back the payload!",
"users": [
"mick",
"nick",
"rick",
"pickle",
],
"companies": [
"apple",
"dell",
"microsoft",
]
}
}

Now let’s replace laravel-test-2 with a local version of the project so we can add some more details to our payload in real-time, without requiring any Docker container builds.

minikube dashboard

Under the Overview section, delete the Deployment, Service and Config Maps for laravel-test-2 or run:

kubectl delete deployment laravel-test-2
kubectl delete service laravel-test-2
kubectl delete configmap laravel-test-2-config

Now run the following Telepresence command inside of the laravel-test-2 folder:

telepresence --new-deployment laravel-test-2 --expose 8080

When this is done, be sure you are in the site directory and run:

php artisan serve --port=8080

Now you will be able to access your project at (laravel-test-2)

http://127.0.0.1:8080/api/payload

and at (laravel-test-1 “/”)

http://192.168.99.103:31010/

so to test the real-time development capabilities, let’s just update our payload with additional items, refresh the laravel-test-1 “/” route and you should see your new items. You can always check at the local host directory too.

Example updated payload in laravel-test-2 /api/payload route:

Route::get('payload', function () {
return response()->json([
'message' => 'Updated payload v2!',
'users' => ['mick', 'nick', 'rick', 'pickle', 'allan', 'steve'],
'companies' => ['apple', 'dell', 'microsoft', 'razer', 'hp'],
], 200);
});

Which results in a JSON response over at the laravel-test-1 project of:

// 20190211153857
// http://192.168.99.103:31010/
{
"status": 200,
"content-type": "application/json",
"response": {
"message": "Updated payload v2!",
"users": [
"mick",
"nick",
"rick",
"pickle",
"allan",
"steve"
],
"companies": [
"apple",
"dell",
"microsoft",
"razer",
"hp"
]
}
}

No Docker builds required! Hell yeah!

Once Your Changes Are Done

Now that you’ve made your ground breaking microservice changes, you simply rebuild the container, tag it with a bumped up tag number, update the deployment.yaml file with the new number (i.e. 0.0.1 to 0.0.2) and run:

kubectl apply -f .

Conclusion

I tried many ways to get Telepresence — swap-deployment to work but just couldn’t get it to read the project locally. If I find a way to achieve this, I will update this article but for now, we’ve simply taken out the microservice, booted up a new Telepresence version of the local version, did some updates as required and once happy with the changes, rebuilt in Docker and redeployed back to Kubernetes or Minikube. Telepresence can be stopped any time by press ctrl + c in the terminal window.