<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ahmed Hanafy</title>
    <description>The latest articles on DEV Community by Ahmed Hanafy (@ahmeddrawy).</description>
    <link>https://dev.to/ahmeddrawy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F399452%2Ff950e67a-7193-4db4-965e-92c6ce99e174.jpeg</url>
      <title>DEV Community: Ahmed Hanafy</title>
      <link>https://dev.to/ahmeddrawy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ahmeddrawy"/>
    <language>en</language>
    <item>
      <title>Database (Schema) migration to Kubernetes - initContainers vs k8s jobs -</title>
      <dc:creator>Ahmed Hanafy</dc:creator>
      <pubDate>Mon, 12 Apr 2021 11:45:31 +0000</pubDate>
      <link>https://dev.to/ahmeddrawy/database-schema-migration-to-kubernetes-initcontainers-vs-k8s-jobs-4a4f</link>
      <guid>https://dev.to/ahmeddrawy/database-schema-migration-to-kubernetes-initcontainers-vs-k8s-jobs-4a4f</guid>
      <description>&lt;h1&gt;
  
  
  Introducton
&lt;/h1&gt;

&lt;p&gt;We in &lt;a href="https://trackxy.com/" rel="noopener noreferrer"&gt;trackxy&lt;/a&gt; were migrating from microservices managed each by its own team to our &lt;a href="https://kubernetes.io/" rel="noopener noreferrer"&gt;Kubernetes&lt;/a&gt; cluster, where we have our services managed.&lt;/p&gt;

&lt;h1&gt;
  
  
  Problem
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;First, we need to know what schema migration is&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;schema migration (also database migration, database change management) refers to the management of incremental, reversible changes and version control to relational database schemas. A schema migration is performed on a database whenever it is necessary to update or revert that database's schema to some newer or older version. &lt;br&gt;
Migrations are performed programmatically by using a schema migration tool. When invoked with a specified desired schema version, the tool automates the successive application or reversal of an appropriate sequence of schema changes until it is brought to the desired state.&lt;br&gt;
&lt;a href="https://en.wikipedia.org/wiki/Schema_migration#:~:text=In%20software%20engineering%2C%20schema%20migration,control%20to%20relational%20database%20schemas." rel="noopener noreferrer"&gt;source&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;So, as mentioned above schema migration is good right? yes it's When we have one instance of the program which runs the schema migration we're free to migrate it anytime we need and we're not worried about syncing or which instance will migrate first but when instances are more than 1 we face race condition and problem with which instance should run the migration &lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Brainstorming
&lt;/h1&gt;

&lt;p&gt;In this section, we'll try to engage you in our thinking process and the pros and cons of each solution and why we chose the solution we did.&lt;/p&gt;

&lt;h3&gt;
  
  
  Encaspulating Migration inside image
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;we first thought about encapsulating the &lt;strong&gt;migration process&lt;/strong&gt; inside the &lt;code&gt;image&lt;/code&gt; itself but there are 3 reasons why we didn't go for this option

&lt;ol&gt;
&lt;li&gt;SoC (&lt;em&gt;Separation of Concerns&lt;/em&gt;), because it's not the image duty to migrate the database &lt;/li&gt;
&lt;li&gt; Race condition when we have several replicas of the image need to migrate at the same time and  &lt;em&gt;Kubernetes&lt;/em&gt; pods creation time can't be controlled&lt;/li&gt;
&lt;li&gt; Ambiguous behaviors, multiple replicas trying to migrate at the same time leads to ambiguous behaviors which we couldn't track and we chose not to go forward with this because the deployment component  in Kubernetes  creates pods in arbitrary order and all pods can start the migration process and make inconsistency in the database&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;due to the reasons we mentioned above we decided not to go forward with this solution
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4zfrmgkgg6mdldpk56ob.png" alt="Schema migration in multiple replicas reaching same database"&gt;
This diagram shows the race condition happens when 3 replicas tries to migrate schema at the same time&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  InitContainers
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;It's nearly similar to the previous solution and has the same cons&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Kubernetes job
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;A Job creates one or more Pods and will continue to retry execution of the Pods until a specified number of them successfully terminate. As pods successfully completed, the Job tracks the successful completions. When a specified number of successful completions is reached, the task (ie, Job) is complete. Deleting a Job will clean up the Pods it created.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/job/" rel="noopener noreferrer"&gt;source&lt;/a&gt;&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;So as mentioned above the Kubernetes job functionality is to create one pod which do a specific job then dies without restarting, and that's exactly what we needed to run our migration once and in one pod only&lt;/p&gt;

&lt;h1&gt;
  
  
  Our Solution
&lt;/h1&gt;

&lt;h2&gt;
  
  
  prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;This solution assumes you have a good background about &lt;strong&gt;&lt;em&gt;docker&lt;/em&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;em&gt;Kubernetes&lt;/em&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;helm&lt;/em&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Having &lt;code&gt;kubectl&lt;/code&gt; on your local machine and configured to your cluster&lt;/li&gt;
&lt;li&gt;You have a Kubernetes up and running -we're not discussing configuring k8s cluster here-

&lt;ul&gt;
&lt;li&gt;if not you can use &lt;em&gt;&lt;a href="https://kubernetes.io/docs/tutorials/hello-minikube/" rel="noopener noreferrer"&gt;Minikube&lt;/a&gt;&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;We will use PHP/Laravel here to demonstrate our solution&lt;/li&gt;

&lt;li&gt;we have a PHP/Laravel pod

&lt;ul&gt;
&lt;li&gt;we configured our Database with PHP&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Our Database up and running -either in the cluster or outside the cluster-&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Use case
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;We need to add a new table to our database
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:migration create_flights_table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Migration should be done and the PHP/Laravel image redeployed, this where our solution comes to solve this problem&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;we eventually went with the Kubernetes job solution&lt;/li&gt;
&lt;li&gt;This is an example of what we have done to migrate the database&lt;/li&gt;
&lt;li&gt;you have to have a &lt;code&gt;values.yaml&lt;/code&gt; file to refer to the values we referred to here using &lt;strong&gt;HELM&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;batch/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Job&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;include "APPLICATION.fullname" .&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;-database-migration-job&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;include "APPLICATION.fullname" .&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;-database-migration-job&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;.Values.databaseMigration.image&lt;/span&gt;&lt;span class="nv"&gt;  &lt;/span&gt;&lt;span class="s"&gt;}}:{{.Values.databaseMigration.imageTag}}"&lt;/span&gt;
          &lt;span class="na"&gt;imagePullPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Values.image.pullPolicy&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
          &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;php&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;artisan&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;migrate&lt;/span&gt;
          &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ENV_VARIABLE&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;
          &lt;span class="na"&gt;envFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;configMapRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;include "APPLICATION.fullname" .&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;secretRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Values.laravel.secrets_names.laravel_secrets&lt;/span&gt;&lt;span class="pi"&gt;}}&lt;/span&gt;
      &lt;span class="na"&gt;restartPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Never&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Run
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;to run the job and deploy using &lt;strong&gt;HELM&lt;/strong&gt; we run in the root directory
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;helm  upgrade --install -n [NAMESPACE] api  .&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;to run the job using &lt;code&gt;kubectl&lt;/code&gt; , you have to fill the variables instead of using &lt;code&gt;values.yaml&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;kubectl apply -f job.yaml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Testing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You can see the job and the pod it looks after like so:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get &lt;span class="nb"&gt;jobs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv7sqtl3tmq7uejnf629v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv7sqtl3tmq7uejnf629v.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;we can see that the pod for the job is created, ran and stopped after completing&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0k0eklgrbqs4jehbqgxj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0k0eklgrbqs4jehbqgxj.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;we can see the logs of the pod to make sure that our migration is done well&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl logs &lt;span class="o"&gt;[&lt;/span&gt;pod-name]
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F358s5lvytvgqhkvhhxcz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F358s5lvytvgqhkvhhxcz.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  invoking job
&lt;/h3&gt;

&lt;p&gt;First we need to know that Kubernetes jobs don't rerun by default, we found that we have to deploy the job every single time you deploy so we decided to redeploy every time using our CI/CD pipeline - outside this article scope -, you can contact me to help you setup your pipeline&lt;/p&gt;

&lt;h3&gt;
  
  
  author
&lt;/h3&gt;

&lt;p&gt;written by me  &lt;a href="https://github.com/ahmeddrawy" rel="noopener noreferrer"&gt;Ahmed Hanafy&lt;/a&gt; feel free to contact me if you have any questions &lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>database</category>
      <category>devops</category>
      <category>laravel</category>
    </item>
  </channel>
</rss>
