Posted on: April 25, 2024 11:33 PM
Posted by: Renato
Categories: Docker Laravel Kubernetes kube KubeCon GitOps containers
Views: 511
Running Dockerized Laravel Applications On Top Of Kubernetes
.
This article includes the steps needed to build a to-do app in Laravel, deploying the app to a server, database migration, and Laravel's Eloquent models.
Related posts
Cloud-Native Now Webinar: Containers Kubernetes Management
Kubernetes Security - A Complete Guide to Securing Your Containers
KubeCon EU 2023 Recap – GitOps Sessions on Flux with OCI, Liquid Metal CI/CD Platforms & Telco Cloud Platforms
Laravel is a free, open-source PHP framework, created by Taylor Otwell and intended for the development of web applications following the model–view–controller (MVC) architectural pattern.
In this article, we’ll be building a Laravel app, dockerizing it, and deploying it on top of Kubernetes. But first, you may be wondering why we’re using Kubernetes and Docker for the deployment of our Laravel application. Kubernetes allows us to derive maximum utility from containers and build cloud-native applications that can run anywhere, independent of cloud-specific requirements. With Kubernetes, we can automatically deploy, scale, and manage our application so that we can pay more attention to building efficient applications. We will go through the steps required in building a To-do app in Laravel and automating the deployment of the app to a server. We'll learn how Laravel handles database migration as well as the Laravel's Eloquent models. Of course, we'll see the Routing and View Layouts. All basic stuff, but fundamental elements of Laravel.
Step 1: Install Laravel
The first thing you have to do is to install composer from https://getcomposer.org/ and then install Laravel, check Laravel’s documentation for that https://laravel.com/docs. At this point, we’ll need to pay close attention to the codeblocks that we are going to use for the building of our Laravel application because, without the correct codeblocks, we will have no dockerizing nor deployment of our app.
Step 2: Create Your Laravel App
To create our new laravel app, run this:
$ laravel new todo
Set Up Database Configuration
Create a database in your preferred database app, then set-up your project-dir/.env file like:
DB_HOST=localhost
DB_CONNECTION=mysql
DB_DATABASE=tasks
DB_USERNAME=root
DB_PASSWORD=""
Create Database Migration
Create a database migration for ‘tasks’ :
$ php artisan make:migration create_tasks_table --create=tasks
The migration will be placed in the database/migrations directory of our project. Add a string column to hold the name of the tasks by adding $table->string('name'); to create_task_table.php file like this :
public function up()
{
Schema::create('tasks', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
});
Then run:
$ php artisan migrate
Create A Model
Next, define a Task model that corresponds to our tasks database table. Run :
$ php artisan make:model Task
Routing
All Laravel routes are defined in the app/Http/routes.php.
Our app needs three routes:
- GET: to display the view
- POST: for updating tasks
- DELETE: to delete tasks
Put all of these routes in app/Http/routes.php file:
'web'], function () {
/**
* Show Task Dashboard
*/
Route::get('/', function () {
$tasks = Task::orderBy('created_at', 'asc')->get();
return view('tasks', [
'tasks' => $tasks
]);
});
/**
* Add New Task
*/
Route::post('/task', function (Request $request) {
$validator = Validator::make($request->all(), [
'name' => 'required|max:255',
]);
if ($validator->fails()) {
return redirect('/')
->withInput()
->withErrors($validator);
Blades Layout For Our View
Define a new layout view in resources/views/layouts/app.blade.php. The .blade.php extension instructs the framework to use the Blade templating engine to render the view.
The new app.blade.php view would look like this:
<!DOCTYPE html>
<html lang="en”>
<head>
<title>Laravel Quickstart - Basic</title>
<!-- CSS And JavaScript -->
</head>
<body>
<div class="container">
<nav class="navbar navbar-default">
<!-- Navbar Contents -->
</nav>
</div>
@yield('content')
</body>
</html>
Create A Form And A Table Listing For The Task
Define a view (resources/views/tasks.blade.php) that contains a form to create a new task as well as a table that lists all existing tasks.
Define this view in resources/views/tasks.blade.php like this:
@extends('layouts.app')
@section('content')
<!-- Bootstrap Boilerplate... -->
<div class="panel-body">
<!-- Display Validation Errors -->
@include('common.errors')
<!-- New Task Form -->
<form action="{{ url('task') }}" method="POST" class="form-horizontal">
{!! csrf_field() !!}
<!-- Task Name -->
<div class="form-group">
<label for="task" class="col-sm-3 control-label">Task</label>
<div class="col-sm-6">
<input type="text" name="name" id="task-name" class="form-control">
</div>
</div>
<!-- Add Task Button -->
<div class="form-group">
<div class="col-sm-offset-3 col-sm-6">
<button type="submit" class="btn btn-default">
<i class="fa fa-plus"></i> Add Task
</button>
</div>
</div>
</form>
</div>
<!-- TODO: Current Tasks -->
@endsection
Error Validation View
Define the contents of the view resources/views/common/errors.blade.php. It should look like this:
@if (count($errors) > 0)
<!-- Form Error List -->
<div class="alert alert-danger">
<strong>Whoops! Something went wrong!</strong>
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
Complete The View
@extends('layouts.app')
@section('content')
<!-- Bootstrap Boilerplate... -->
<div class="panel-body">
<!-- Display Validation Errors -->
@include('common.errors')
<!-- New Task Form -->
<form action="{{ url('task') }}" method="POST" class="form-horizontal">
{!! csrf_field() !!}
<!-- Task Name -->
<div class="form-group">
<label for="task" class="col-sm-3 control-label">Task</label>
<div class="col-sm-6">
<input type="text" name="name" id="task-name" class="form-control">
</div>
</div>
<!-- Add Task Button -->
<div class="form-group">
<div class="col-sm-offset-3 col-sm-6">
<button type="submit" class="btn btn-default">
<i class="fa fa-plus"></i> Add Task
</button>
</div>
</div>
</form>
</div>
<!-- Current Tasks -->
@if (count($tasks) > 0)
<div class="panel panel-default">
<div class="panel-heading">
Current Tasks
</div>
<div class="panel-body">
<table class="table table-striped task-table">
<!-- Table Headings -->
<thead>
<th>Task</th>
<th> </th>
</thead>
<!-- Table Body -->
<tbody>
@foreach ($tasks as $task)
<tr>
<!-- Task Name -->
<td class="table-text">
<div>{{ $task->name }}</div>
</td>
<!-- Delete Button -->
<td>
<form action="{{ url('task/'.$task->id) }}" method="POST">
{!! csrf_field() !!}
{!! method_field('DELETE') !!}
<button>Delete Task</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@endif
@endsection
Run App On Local
After successfully building our app, we’ll be running the app locally using the following commands:
$ php artisan serve
Step 3: Dockerize Your Laravel Project
There’s a number of ways to dockerize your Laravel project. You may use the official Nginx and PHP images from Dockerhub, but we found it’s a bit troublesome to set them up.
So instead of messing around with all the different kinds of docker images, we discovered docker-images-php, a set of production-ready docker images.
In building your project, your Dockerfile should look like this:
ARG PHP_EXTENSIONS="apcu bcmath opcache pcntl pdo_mysql redis zip sockets imagick gd exif"
FROM thecodingmachine/php:7.3-v2-slim-apache as php_base
ENV TEMPLATE_PHP_INI=production
COPY --chown=docker:docker . /var/www/html
RUN composer install --quiet --optimize-autoloader --no-dev
FROM node:10 as node_dependencies
WORKDIR /var/www/html
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=false
COPY --from=php_base /var/www/html /var/www/html
RUN npm set progress=false && \
npm config set depth 0 && \
npm install && \
npm run prod && \
rm -rf node_modules
FROM php_base
COPY --from=node_dependencies --chown=docker:docker /var/www/html /var/www/html
Step 4: Create Your Kubernetes Deployment Files
There are a couple of yaml files required to set up the deployment of the Laravel project on Kubernetes:
configmap.yaml
The ConfigMap is the section of your code that stores the configuration settings for your application to work, or for a Kubernetes pod to work. Now while writing your configmap, you have to consider your App_DEBUG input on line 6 “APP_DEBUG: "false"” making sure your application is not debugged and inputting your APP_NAME on line 10 APP_NAME: "Laravel-Todo" correctly so as not to send your deployment on a wild goose chase.
apiVersion: v1
kind: ConfigMap
metadata:
name: laravel-todo
data:
APP_DEBUG: "false"
APP_ENV: production
APP_KEY: changeme
APP_LOG_LEVEL: debug
APP_NAME: "Laravel-Todo"
APP_URL: https://www.laravel.com
BROADCAST_DRIVER: pusher
CACHE_DRIVER: redis
QUEUE_DRIVER: redis
SESSION_DRIVER: redis
DB_CONNECTION: mysql
DB_DATABASE: task
DB_HOST: host
DB_PASSWORD: password
DB_PORT: "3306"
DB_USERNAME: username
REDIS_HOST: redis
REDIS_PORT: "6379"
deployment.yaml
A Deployment resource uses a ReplicaSet to manage the pods. However, it handles updating them in a controlled way.
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: laravel-todo
name: laravel-todo
spec:
minReadySeconds: 5
replicas: 3
revisionHistoryLimit: 1
selector:
matchLabels:
app: laravel-todo
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 50%
type: RollingUpdate
template:
metadata:
labels:
app: laravel-todo
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- backend
topologyKey: kubernetes.io/hostname
weight: 100
initContainers:
- args:
- /bin/bash
- -c
- (php artisan migrate || true) && (php artisan config:cache || true) && (php
artisan route:cache || true) && (cp -rp /var/www/html /codebase)
envFrom:
- configMapRef:
name: laravel-todo-config
image: changeme
imagePullPolicy: Always
name: artisan
volumeMounts:
- mountPath: /codebase
name: codebase
containers:
- name: app
envFrom:
- configMapRef:
name: laravel-todo-config
image: changeme
imagePullPolicy: Always
livenessProbe:
initialDelaySeconds: 10
periodSeconds: 15
tcpSocket:
port: 80
timeoutSeconds: 30
ports:
- containerPort: 80
readinessProbe:
initialDelaySeconds: 10
periodSeconds: 10
tcpSocket:
port: 80
resources:
limits:
cpu: 200m
memory: 400M
requests:
cpu: 100m
memory: 200M
volumeMounts:
- mountPath: /var/www
name: codebase
volumes:
- emptyDir: {}
name: codebase
ingress.yaml
Ingress brings in, and exposes, your HTTP and HTTPS routes that are outside the cluster into services that are inside the cluster.
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/from-to-www-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: 100m
labels:
app: laravel-todo
name: laravel-todo
spec:
rules:
- host: yourdomain.com
http:
paths:
- laravel-todo:
serviceName: laravel-todo
servicePort: 80
path: /
# tls:
# - hosts:
# - yourdomain.com
# secretName: yourdomain-com-tls
# ---
# apiVersion: certmanager.k8s.io/v1alpha1
# kind: Certificate
# metadata:
# name: yourdomain.com
# spec:
# acme:
# config:
# - dns01:
# provider: cf-dns
# domains:
# - yourdomain.com
# commonName: yourdomain.com
# dnsNames:
# - yourdomain.com
# issuerRef:
# kind: ClusterIssuer
# name: letsencrypt
# secretName: yourdomain-com-tls
service.yaml
Service does only one thing, and that’s exposing your running applications on a specified set of pods as a network.
apiVersion: v1
kind: Service
metadata:
labels:
app: laravel-todo
name: laravel-todo
spec:
ports:
- name: http
port: 80
protocol: TCP
selector:
app: laravel-todo
type: ClusterIP
Step 5: Configure GitLab CI/CD
Now that we have our Laravel To-do app, Dockerfile, and Kubernetes configurations ready, go ahead and set up Gitlab Continuous Integration to automate our deployment making use of Kubernetes token authentication.
Set Up CI/CD Environment Variables
Within Repo goto Settings then CI / CD, we need to store our Kubernetes Cluster credentials into Gitlab CI’s environment variables.
Move the Dockerfile to your project’s root directory, then create a folder called k8 and store all the Kubernetes yaml files inside. Create a file called .gitlab-ci.yml that contains the following:
variables:
DOCKER_DRIVER: "overlay2"
REPOSITORY_URL: "url_name:latest"
stages:
- build
- deploy
services:
- docker:dind
build-app:
stage: build
only:
- master
script:
- mkdir -p $HOME/.docker
- apk add --no-cache curl jq python py-pip
- pip install awscli
- $(aws ecr get-login --no-include-email --region ap-southeast-1)
- docker info
- docker build -t ${REPOSITORY_URL} .
- docker push ${REPOSITORY_URL}
deploy-app:
stage: deploy
image: alpine
only:
- master
script:
- apk add --no-cache curl
- curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s
https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
- chmod +x ./kubectl
- mv ./kubectl /usr/local/bin/kubectl
- kubectl config set-cluster k8s --server="$KUBE_URL" --insecure-skip-tls-verify=true
- kubectl config set-credentials admin --token="$KUBE_TOKEN"
- kubectl config set-context default --cluster=k8s --user=admin
- kubectl config use-context default
- cd k8s && kubectl apply -f -
- kubectl patch deployment laravel-todo -p
"{\"spec\":{\"template\":{\"metadata\":{\"labels\":{\"date\":\"`date +'%s'`\"}}}}}"
The final step is to push your code to the repo and wait for it to be deployed automatically and without any downtime.
Step 6: Deploy And Access Your Application
After pushing your Laravel code to the repository, it’s automatically deployed. Navigate to your URL on a browser to check your application. It should look like this:
You can then begin to add and delete tasks (as needed) on your deployed application.
TL;DR
- Laravel is a free, open-source PHP framework, created by Taylor Otwell and intended for the development of web applications following the model–view–controller (MVC) architectural pattern.
- Docker is a tool designed to make it easy to create, deploy and run applications by using containers. A container packages all the code and all its dependencies as one unit for quick running from one computing environment to another.
- There are only a couple of yaml files required to set up the deployment of the Laravel project on Kubernetes.
- Gitlab Continuous Integration is used to automate our deployment, making use of Kubernetes token authentication.
Fonte:
- https://www.weave.works/blog/running-dockerized-laravel-applications-on-top-of-kubernetes
- https://web.archive.org/web/20231201073026/https://www.weave.works/blog/running-dockerized-laravel-applications-on-top-of-kubernetes
Donate to Site
Renato
Developer