<?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: pongsatt</title>
    <description>The latest articles on DEV Community by pongsatt (@pongsatt).</description>
    <link>https://dev.to/pongsatt</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%2F84687%2F031652a3-29ad-4794-94a9-1354dc9f63a2.jpeg</url>
      <title>DEV Community: pongsatt</title>
      <link>https://dev.to/pongsatt</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pongsatt"/>
    <language>en</language>
    <item>
      <title>Setup your own kubernetes cluster on VMs</title>
      <dc:creator>pongsatt</dc:creator>
      <pubDate>Sat, 03 Nov 2018 07:03:04 +0000</pubDate>
      <link>https://dev.to/pongsatt/setup-your-own-kubernetes-cluster-on-vms-5ln</link>
      <guid>https://dev.to/pongsatt/setup-your-own-kubernetes-cluster-on-vms-5ln</guid>
      <description>&lt;p&gt;My requirement is that I want a server I can can play around. I tried Docker with Jenkins CD and it was awesome. Then Docker swarm and Kubernetes came so I wanted to upgrade my Docker environment to a cluster. The problem is that I've got only a PC and I don't want to pay for my playground environment. Then I found that I can achieve this using a tool named kubeadm and some virtual machines. I would like to share my environment and how to set it up as well as my record for steps I used.&lt;/p&gt;

&lt;p&gt;I divided the steps into a series of posts so it's easy and concise enough to follow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Requirement&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A kubernetes cluster based on kubeadm&lt;/li&gt;
&lt;li&gt;Support dynamic storage provisioning based on NFS&lt;/li&gt;
&lt;li&gt;Support Ingress with automatically acquire let's encrypt certificate&lt;/li&gt;
&lt;li&gt;Be able to deploy application using Jenkin CD pipeline for both backend and frontend&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can follow each step in each post below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup detail:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://dev.to/pongsatt/prepare-virtual-machine-using-virtualbox-and-ubuntu-558j"&gt;Prepare Virtual Machine using Virtualbox and Ubuntu&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/pongsatt/setup-kubernetes-cluster-using-kubeadm-hpb"&gt;Setup kubernetes cluster using kubeadm (1 master 3 nodes)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/pongsatt/setup-dynamic-storage-class-provisioner-based-on-nfs-1d4i"&gt;Setup dynamic storage class provisioner based on NFS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/pongsatt/set-up-nginx-ingress-with-letsencrypt-certificate-on-vms-or-bare-metal-kubernetes-cluster-j25"&gt;Setup nginx ingress and cert-manager to automatically apply letsencrypt certificate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/pongsatt/deploy-static-front-end-application-to-kubernetes-cluster-using-jenkins-cd-and-minio-with-automatic-https-cert-44gk"&gt;Deploy static front-end application to kubernetes cluster using Jenkins CD and minio with automatic https cert&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/pongsatt/deploy-back-end-api-application-with-database-to-the-kubernetes-cluster-with-jenkins-cd-51j7"&gt;Deploy back-end api application with database to the kubernetes cluster with Jenkins CD&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>kubernetes</category>
      <category>kubeadm</category>
    </item>
    <item>
      <title>Deploy back-end api application with database to the kubernetes cluster with Jenkins CD</title>
      <dc:creator>pongsatt</dc:creator>
      <pubDate>Sat, 03 Nov 2018 07:01:54 +0000</pubDate>
      <link>https://dev.to/pongsatt/deploy-back-end-api-application-with-database-to-the-kubernetes-cluster-with-jenkins-cd-51j7</link>
      <guid>https://dev.to/pongsatt/deploy-back-end-api-application-with-database-to-the-kubernetes-cluster-with-jenkins-cd-51j7</guid>
      <description>&lt;p&gt;We will create a simple graphql api application that connects to mongodb. Then deploy to kubernetes cluster using Jenkins CD.&lt;/p&gt;

&lt;p&gt;In addition, we will create ingress that support https so we can access this application securely.&lt;/p&gt;

&lt;p&gt;This post is part of series &lt;a href="https://dev.to/pongsatt/setup-your-own-kubernetes-cluster-on-vms-5ln"&gt;"Setup your own kubernetes cluster on VMs"&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This post assume that you follow from &lt;a href="https://dev.to/pongsatt/deploy-static-front-end-application-to-kubernetes-cluster-using-jenkins-cd-and-minio-with-automatic-https-cert-44gk"&gt;previous post&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Prerequisite:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A running kubernetes cluster with storage-class support&lt;/li&gt;
&lt;li&gt;Jenkins server with pipeline support&lt;/li&gt;
&lt;li&gt;Docker private repository&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Build Application
&lt;/h1&gt;

&lt;p&gt;We will create a project called "simpleapi" which is a graphql with mongodb application using nodejs.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create project
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir simpleapi
cd simpleapi
yarn init -y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Build graphql api
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add graphql-yoga mongodb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create "db.js" at root folder with content below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const mongoClient = require('mongodb').MongoClient

module.exports = {
    users: () =&amp;gt; execute((coll) =&amp;gt; coll.find().toArray()
        .then(results =&amp;gt; results.map(idToString))),
    createUser: (name) =&amp;gt; execute((coll) =&amp;gt; coll.insertOne({ name })
        .then(result =&amp;gt; idToString(result.ops[0])))
}

function execute(fn) {
    return mongoClient.connect(process.env.DB_URL || 'mongodb://localhost:27017/test')
        .then(client =&amp;gt; {
            const result = fn(client.db().collection('users'))
            client.close()
            return result
        })
}

function idToString(doc) {
    const {_id, ...rest} = doc
    return {_id: _id.toString(), ...rest}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This program connects mongo at url "mongodb://localhost:27017/test" or url from environment name "DB_URL" if provided.&lt;/p&gt;

&lt;p&gt;It also contains 2 functions for querying user list and create new user. We will use these functions in "index.js" below.&lt;/p&gt;

&lt;p&gt;Create "index.js" at root folder with content below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { GraphQLServer } = require('graphql-yoga')
const db = require('./db')

const typeDefs = `
  type User {
      _id: ID
      name: String
  }

  type Query {
    users: [User]
  }

  type Mutation {
      createUser(name: String!): User!
  }
`

const resolvers = {
  Query: {
    users: (_, { }) =&amp;gt; db.users()
  },
  Mutation: {
    createUser: (_, {name}) =&amp;gt; db.createUser(name)
  }
}

const server = new GraphQLServer({ typeDefs, resolvers })
server.start(() =&amp;gt; console.log('Server is running on http://localhost:4000'))

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this program, we define graphql schema with function to query user and user creation and start server at port 4000.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Test graphql api
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You need mongodb running at "mongodb://localhost:27017" to be able to test&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Start server by running &lt;code&gt;node index.js&lt;/code&gt; and open url "&lt;a href="http://localhost:4000" rel="noopener noreferrer"&gt;http://localhost:4000&lt;/a&gt;".&lt;/p&gt;

&lt;p&gt;Create user.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ff5ooc7mf9sjsjk80zng3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ff5ooc7mf9sjsjk80zng3.png" title="Running graphql server" width="800" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Query user.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fa7ku76k7rf9bm3hlekfv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fa7ku76k7rf9bm3hlekfv.png" title="User list" width="800" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Add Jenkins pipeline to project
&lt;/h1&gt;

&lt;p&gt;The api application pipeline will need Dockerfile, kubernetes and Jenkins pipeline configurations.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Dockerfile
&lt;/h3&gt;

&lt;p&gt;Here is the nodejs based Dockerfile configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM node:8.11.0-alpine

WORKDIR /usr/src

COPY ./*.js package.json yarn.lock ./
RUN yarn install

EXPOSE 4000
CMD [ "node", "index.js"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1. Kubernetes for database
&lt;/h3&gt;

&lt;p&gt;Create file "k8s/db.yml" with content below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: simpleapi-db-claim
  annotations:
    volume.beta.kubernetes.io/storage-class: "ssdnfs"
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi
---

apiVersion: v1
kind: ReplicationController
metadata:
  labels:
    name: simpleapi-db
  name: simpleapi-db
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: simpleapi-db
    spec:
      containers:
      - image: mongo
        name: mongo
        ports:
        - name: mongo
          containerPort: 27017
        volumeMounts:
            - name: mongo-persistent-storage
              mountPath: /data/db
      volumes:
        - name: mongo-persistent-storage
          persistentVolumeClaim:
            claimName: simpleapi-db-claim
---
apiVersion: v1
kind: Service
metadata:
  labels:
    name: simpleapi-db
  name: simpleapi-db
spec:
  ports:
    - port: 27017
      targetPort: 27017
  selector:
    app: simpleapi-db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration will create Persistent Volume Claim that use storage class "ssdnfs" we created from &lt;a href="https://dev.to/pongsatt/setup-dynamic-storage-class-provisioner-based-on-nfs-1d4i"&gt;previous post&lt;/a&gt;. We also define mongodb instance and its service.&lt;/p&gt;

&lt;p&gt;We can access this db within cluster using "simpleapi-db:27017".&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Kubernetes for application
&lt;/h3&gt;

&lt;p&gt;Create file "k8s/api.yml" with content.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: simpleapi
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: simpleapi
    spec:
      containers:
        - name: simpleapi
          image: "&amp;lt;imageTag&amp;gt;"
          env:
          - name: DB_URL
            value: "mongodb://simpleapi-db:27017/test"
          ports:
          - name: http
            containerPort: 4000
          readinessProbe:
            httpGet:
              path: /
              port: 4000
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /
              port: 4000
            initialDelaySeconds: 3
            periodSeconds: 20
---
apiVersion: v1
kind: Service
metadata:
  name: simpleapi
spec:
  ports:
    - name: http
      port: 80
      targetPort: 4000
  selector:
    app: simpleapi

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This config composes of a Deployment and Service. We can access this application through service "simpleapi:80".&lt;/p&gt;

&lt;p&gt;We also override database url with environment "DB_URL".&lt;/p&gt;

&lt;p&gt; will be replaced in the build process since it's generated later.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Kubernetes for ingress
&lt;/h3&gt;

&lt;p&gt;As promise, this api will be able to access through https via ingress configuration (Setup from &lt;a href="https://dev.to/pongsatt/set-up-nginx-ingress-with-letsencrypt-certificate-on-vms-or-bare-metal-kubernetes-cluster-j25"&gt;previous post&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Create file "k8s/ingress.yml".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Notes:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you apply letsencrypt certificate using http01 protocol, you will need to uncomment/comment annotations.&lt;br&gt;
Replace "simpleapi.yourdomain.com" with your real domain name&lt;br&gt;
You will need to create DNS record for "simpleapi.yourdomain.com" so that it can apply for certificate (See &lt;a href="https://dev.to/pongsatt/deploy-static-front-end-application-to-kubernetes-cluster-using-jenkins-cd-and-minio-with-automatic-https-cert-44gk"&gt;previous post&lt;/a&gt; for more detail)&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: simpleapi-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"

    # use http01 protocol. uncomment line below to use http01
    # kubernetes.io/tls-acme: "true"

    # use dns01 protocol. comment 2 lines below if use http01
    certmanager.k8s.io/acme-challenge-type: "dns01"
    certmanager.k8s.io/acme-dns01-provider: "aws-route53"

spec:
  tls:
  - hosts:
    - simpleapi.yourdomain.com
    secretName: simpleapi-tls
  rules:
  # The host must be identical to the above one
  - host: simpleapi.yourdomain.com
    http:
      paths:
      - path: /
        backend:
          # The name of your service
          serviceName: simpleapi
          servicePort: 80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Jenkinsfile
&lt;/h3&gt;

&lt;p&gt;Create "Jenkinsfile" at the root folder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Notes:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Replace "192.168.1.105:8082" with your private registry&lt;br&gt;
You need to create "User/password" credential name "myregUserPass" to access your Docker repository in Jenkins&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pipeline {
    environment {
        IMAGE_TAG = "${env.BUILD_NUMBER}"
        REGISTRY = "192.168.1.105:8082"
        APP = "simpleapi"
    }
    agent { label 'docker' }

    stages {
        stage('build and push image') {
            steps {
                script {
                    docker.withRegistry("http://${REGISTRY}", 'myregUserPass') {
                        docker.build("${APP}").push("${IMAGE_TAG}")
                    }
                }
            }
        }
        stage('deploy') {
            steps {
                sh("sed -i.bak 's|&amp;lt;imageTag&amp;gt;|${REGISTRY}/${APP}:${IMAGE_TAG}|' ./k8s/api.yml")
                sh("kubectl apply -f k8s/")
            }
        }

    }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are 2 stages in this pipeline.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;build and push image: build and push new image to docker private repo&lt;/li&gt;
&lt;li&gt;deploy: replace  and apply all yml files&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At this point, your project folders should look like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F32chi85vb33g66fjr71a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F32chi85vb33g66fjr71a.png" title="Folders" width="722" height="608"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Publish project to git repository
&lt;/h3&gt;

&lt;p&gt;You can push to any git. We will use this to configure Jenkins in next step.&lt;/p&gt;

&lt;h1&gt;
  
  
  Configure kubernetes for docker private registry
&lt;/h1&gt;

&lt;p&gt;Kubernetes cluster will need to know user and password in order to pull image from it. We can set it up by patching service account that pull the image, in this case, default service account.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create secret
&lt;/h3&gt;

&lt;p&gt;On your master node.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Notes:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Replace  with your private registry server&lt;br&gt;
Replace    with your registry data&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl create secret docker-registry dockerconfig --docker-server=&amp;lt;your-registry-server&amp;gt; --docker-username=&amp;lt;your-name&amp;gt; --docker-password=&amp;lt;your-pword&amp;gt; --docker-email=&amp;lt;your-email&amp;gt;

kubectl patch serviceaccount default -p '{"imagePullSecrets": [{"name": "dockerconfig"}]}'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Create Jenkins Pipeline
&lt;/h1&gt;

&lt;p&gt;Now, we will create new pipeline project in Jenkins called "simpleapi".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fe6bcn552z36tsvhh9nlo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fe6bcn552z36tsvhh9nlo.png" title="Pipeline" width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Set git repository and credential.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fcnh2c1qzayuknhz126yu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fcnh2c1qzayuknhz126yu.png" title="Git repo and credential" width="800" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Run build and wait until success.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ffcabu5re3q0nalq7lm4y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ffcabu5re3q0nalq7lm4y.png" title="build success" width="800" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;kubectl get secret&lt;/code&gt;. If you see "simpleapi-tls", means we can access our application through "&lt;a href="https://simpleapi.yourdomain.com" rel="noopener noreferrer"&gt;https://simpleapi.yourdomain.com&lt;/a&gt;".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F0ki3zdgvokc3ohpg6i1i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F0ki3zdgvokc3ohpg6i1i.png" title="api result" width="800" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;This is the last post of this &lt;a href="https://dev.to/pongsatt/setup-your-own-kubernetes-cluster-on-vms-5ln"&gt;series&lt;/a&gt;. We learned how to setup cluster until create front and back end with ingress and dynamic storage. I hope these posts provide some values for you. Thanks.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>graphql</category>
      <category>jenkins</category>
      <category>mongodb</category>
    </item>
    <item>
      <title>Deploy static front-end application to kubernetes cluster using Jenkins CD and minio with automatic https cert</title>
      <dc:creator>pongsatt</dc:creator>
      <pubDate>Sat, 03 Nov 2018 06:59:21 +0000</pubDate>
      <link>https://dev.to/pongsatt/deploy-static-front-end-application-to-kubernetes-cluster-using-jenkins-cd-and-minio-with-automatic-https-cert-44gk</link>
      <guid>https://dev.to/pongsatt/deploy-static-front-end-application-to-kubernetes-cluster-using-jenkins-cd-and-minio-with-automatic-https-cert-44gk</guid>
      <description>&lt;p&gt;After we've setup kubernetes cluster on our VMs, we setup nginx ingress automatically apply for certificate with letsencrypt. This post we will deploy static web application using Jenkins CD and minio.&lt;/p&gt;

&lt;p&gt;With Jenkins CD, we can define our build and deployment pipeline, and with the power of kubernetes and docker, our application infrastructure also defined along with our source code.&lt;/p&gt;

&lt;p&gt;With minio, a file storage server, we can host static web site as a folder like we do with AWS S3 but within our own infrastructure.&lt;/p&gt;

&lt;p&gt;Then with nginx ingress that we setup from &lt;a href="https://dev.to/pongsatt/set-up-nginx-ingress-with-letsencrypt-certificate-on-vms-or-bare-metal-kubernetes-cluster-j25"&gt;previous post&lt;/a&gt;, we can point website URL to minio file serving.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This post assume that you follow this &lt;a href="https://dev.to/pongsatt/setup-your-own-kubernetes-cluster-on-vms-5ln"&gt;series&lt;/a&gt; of posts&lt;br&gt;
If you follow this &lt;a href="https://dev.to/pongsatt/setup-your-own-kubernetes-cluster-on-vms-5ln"&gt;series&lt;/a&gt;, you need to be on master node to follow this post&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Prerequisite:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A running kubernetes cluster&lt;/li&gt;
&lt;li&gt;helm installed&lt;/li&gt;
&lt;li&gt;Jenkins server with pipeline support&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Setup minio
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;minio requires access key and secret. Please replace "myaccesskey" and "mysecret" with you own values&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  1. Install minio using helm
&lt;/h3&gt;

&lt;p&gt;This run will install minio server in "minio" namespace&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm install --name minio --namespace minio stable/minio \
--set accessKey=myaccesskey,secretKey=mysecret \
--set service.type=NodePort

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Install minio web client
&lt;/h3&gt;

&lt;p&gt;Install mc cli using command below (need root password).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wget https://dl.minio.io/client/mc/release/linux-amd64/mc &amp;amp;&amp;amp; chmod +x mc &amp;amp;&amp;amp; sudo mv mc /usr/local/bin/mc

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Configure minio pointing to the server
&lt;/h3&gt;

&lt;p&gt;Run &lt;code&gt;kubectl get service -n minio&lt;/code&gt; to find NodePort.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F4bap9h7cq3epurxujqwh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F4bap9h7cq3epurxujqwh.png" title="NodePort" width="800" height="85"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Run command to add minio configuration folder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Replace  with your real master node IP address&lt;br&gt;
Replace  with real node port from above step&lt;br&gt;
Replace "myaccesskey" and "mysecret" with your own values&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mc config host add myminio http://&amp;lt;Master Node IP&amp;gt;:&amp;lt;NodePort&amp;gt; myaccesskey mysecret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result should look like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fyq1znp7iiyekx5e7ebm5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fyq1znp7iiyekx5e7ebm5.png" title="Add success" width="800" height="64"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Create minio folder to host static files
&lt;/h3&gt;

&lt;p&gt;In this example, we will create a bucket name "public" to host all static folders.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mc mb -p myminio/public
mc policy download myminio/public
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;mc ls myminio&lt;/code&gt; and you should see an empty bucket.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fxwvg7gvps8dah6vwrodh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fxwvg7gvps8dah6vwrodh.png" title="Empty bucket" width="622" height="116"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Setup DNS record
&lt;/h1&gt;

&lt;p&gt;In order to apply for https domain certificate, you need to set your DNS record "simpleapp.yourdomain.com" to your ingress service IP. You can see my example from &lt;a href="https://dev.to/pongsatt/set-up-nginx-ingress-with-letsencrypt-certificate-on-vms-or-bare-metal-kubernetes-cluster-j25"&gt;previous post&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Build a static website
&lt;/h1&gt;

&lt;p&gt;We will use react application as our sample static website. Let's build it.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Initialize application
&lt;/h3&gt;

&lt;p&gt;Note: you will need nodejs installed on your machine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx create-react-app simpleapp
cd simpleapp
npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open "&lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;" and you should see.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F9ri3pp9z2kk5wyd41ywp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F9ri3pp9z2kk5wyd41ywp.png" title="React local" width="800" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Add kubernetes ingress configuration
&lt;/h3&gt;

&lt;p&gt;Add "ingress.yml" under "k8s" folder at the root folder.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fyjwrfn0ladvwtlz83vfy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fyjwrfn0ladvwtlz83vfy.png" title="ingress.yml" width="722" height="580"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Content:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Replace "simpleapp.yourdomain.com" with real domain&lt;br&gt;
This example use "dns01" protocol to acquire letsencrypt certificate. You can change it to "http01" by comment/uncomment&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: simpleapp-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"

    # use http01 protocol. uncomment line below to use http01
    # kubernetes.io/tls-acme: "true"

    # use dns01 protocol. comment 2 lines below if use http01
    certmanager.k8s.io/acme-challenge-type: "dns01"
    certmanager.k8s.io/acme-dns01-provider: "aws-route53"

    nginx.ingress.kubernetes.io/rewrite-target: "/public/simpleapp"
    nginx.ingress.kubernetes.io/configuration-snippet: |
      rewrite ^/?$ /public/simpleapp/index.html break;
spec:
  tls:
  - hosts:
    - simpleapp.yourdomain.com
    secretName: simpleapp-tls
  rules:
  # The host must be identical to the above one
  - host: simpleapp.yourdomain.com
    http:
      paths:
      - path: /
        backend:
          # The name of your service
          serviceName: minio
          servicePort: 9000

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration will point "simpleapp.yourdomain.com" to a folder under "public" bucket on minio to a folder name "simpleapp". Also support redirect from "/" to "/index.html"&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Add Jenkinsfile pipeline
&lt;/h3&gt;

&lt;p&gt;Create file name "Jenkinsfile" at the root folder.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fbzgr920qqiw7b88oetnu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fbzgr920qqiw7b88oetnu.png" title="Jenkinsfile" width="722" height="646"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pipeline {
  environment {
    ENV = "minio"
    APP = "simpleapp"
  }
  agent { label 'docker' }

  stages {
    stage('build') {
      steps {
        withDockerContainer('node:8.4.0-alpine') { 
            sh 'yarn install'
            sh 'yarn build'
        }
      }
    }

    stage('publish to minio') {
      steps {
        sh "mc cp -r build/ myminio/public/${APP}"
      }
    }

    stage('deploy ingress to kubernetes') {
        steps {
            sh("kubectl --namespace=${ENV} apply -f k8s/ingress.yml")
        }
    }

  }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the configuration, I have a Jenkins build agent with label "docker" that contains "kubernetes" and "mc" cli.&lt;/p&gt;

&lt;p&gt;There are 3 stages.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;build: react project to "build" folder&lt;/li&gt;
&lt;li&gt;publish to minio: copy folder "build" to minio "public/simpleapp"&lt;/li&gt;
&lt;li&gt;deploy ingress to kubernetes: deploy "k8s/ingress.yml" to cluster&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  4. Publish to git repo
&lt;/h3&gt;

&lt;p&gt;You can publish your project to any git repository.&lt;/p&gt;

&lt;h1&gt;
  
  
  Prepare Jenkins environment
&lt;/h1&gt;

&lt;p&gt;In this example, your jenkins needs to support below cli.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;kubectl : with ".kube" configuration folder that point to our kubernetes server&lt;/li&gt;
&lt;li&gt;mc : with ".mc" configuration folder that point to our minio instance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Install these tools will depend on your jenkins environment&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  1. kubectl configuration
&lt;/h3&gt;

&lt;p&gt;You can just copy "~/.kube/config" from master node to "/home/jenkins/.kube/config".&lt;/p&gt;

&lt;h3&gt;
  
  
  2. mc configuration
&lt;/h3&gt;

&lt;p&gt;You can just copy "~/.mc/config.json" from master node to "/home/jenkins/.mc/config.json"&lt;/p&gt;

&lt;p&gt;If all the configurations is ok, you should be able to run command below inside Jenkins agent.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fqpbx9zegw7nvwhn3a6zz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fqpbx9zegw7nvwhn3a6zz.png" title="CLI inside Jenkins" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Build Jenkins pipeline
&lt;/h1&gt;

&lt;p&gt;In this example, we will create Jenkins pipeline that point to Jenkinsfile in the repository.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create Jenkins pipeline project
&lt;/h3&gt;

&lt;p&gt;Create "simpleapp" project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ffhj5l8f5yqc2z89wgq5m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ffhj5l8f5yqc2z89wgq5m.png" title="Create pipeline" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enter your git repo url and credential.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fcnh2c1qzayuknhz126yu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fcnh2c1qzayuknhz126yu.png" title="Git repo and credential" width="800" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Build pipeline
&lt;/h3&gt;

&lt;p&gt;Normally, pipeline build should be automatically triggered when the source code is update. This example will use manual trigger.&lt;/p&gt;

&lt;p&gt;Build result.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F0hzksadfi1pvceagfdwz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F0hzksadfi1pvceagfdwz.png" title="Jenkins build" width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Test your application
&lt;/h1&gt;

&lt;p&gt;Wait until &lt;code&gt;kubectl get secret -n minio&lt;/code&gt; return secret name "simpleapp-tls".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fqrqxmg0ca9d0wkn5ts71.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fqrqxmg0ca9d0wkn5ts71.png" title="Secret result" width="800" height="132"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If everything is ok, you should be able to open URL "&lt;a href="https://simpleapp.yourdomain.com" rel="noopener noreferrer"&gt;https://simpleapp.yourdomain.com&lt;/a&gt;" and see result as below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F1635katlu1g8nmizhdii.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F1635katlu1g8nmizhdii.png" title="https result" width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;We build static website with Jenkins CD, then deploy to minio file server and kubernetes with automatic letsencrpyt certified. &lt;a href="https://dev.to/pongsatt/deploy-back-end-api-application-with-database-to-the-kubernetes-cluster-with-jenkins-cd-51j7"&gt;Next&lt;/a&gt;, we will do CD with backend code and kubernetes cluster.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>minio</category>
      <category>letsencrypt</category>
      <category>jenkins</category>
    </item>
    <item>
      <title>Set up nginx ingress with letsencrypt certificate on VMs or bare metal kubernetes cluster</title>
      <dc:creator>pongsatt</dc:creator>
      <pubDate>Sat, 03 Nov 2018 06:58:41 +0000</pubDate>
      <link>https://dev.to/pongsatt/set-up-nginx-ingress-with-letsencrypt-certificate-on-vms-or-bare-metal-kubernetes-cluster-j25</link>
      <guid>https://dev.to/pongsatt/set-up-nginx-ingress-with-letsencrypt-certificate-on-vms-or-bare-metal-kubernetes-cluster-j25</guid>
      <description>&lt;p&gt;This post is a guide to setup ingress with cert-manager on kubernetes cluster (VMs based) so that the application deploy on the cluster can apply for https certificate automatically.&lt;/p&gt;

&lt;p&gt;When we use kubernetes cluster using cloud providers, they already provide ingress mechanism based on their load balancer technology. If we want this capability on VMs or bare metal based cluster, we need to set it up ourselves.&lt;/p&gt;

&lt;p&gt;It is part of series &lt;a href="https://dev.to/pongsatt/setup-your-own-kubernetes-cluster-on-vms-5ln"&gt;"Setup your own kubernetes cluster on VMs"&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisite:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A running kubernetes cluster&lt;/li&gt;
&lt;li&gt;A domain name with you have administration access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For those who follow along, you need to run everything on master node&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Install helm
&lt;/h1&gt;

&lt;p&gt;First of all, we need helm, a cluster package manager, that will help us install ingress and cert-manager.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create service account and its permission
&lt;/h3&gt;

&lt;p&gt;Run command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo 'apiVersion: v1
kind: ServiceAccount
metadata:
  name: tiller
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: tiller
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: tiller
    namespace: kube-system' | kubectl apply -f -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Install helm
&lt;/h3&gt;

&lt;p&gt;Run below command to install helm from official bash script.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: You will need to enter root password.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl https://raw.githubusercontent.com/helm/helm/master/scripts/get | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Initialize helm
&lt;/h3&gt;

&lt;p&gt;We need to initialize helm to install server application on the cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm init --service-account tiller
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Check installation
&lt;/h3&gt;

&lt;p&gt;Run &lt;code&gt;helm version&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If things are ok, you should see.&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F4ukf0lo1qritsisdeyd5.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F4ukf0lo1qritsisdeyd5.png" title="Helm version"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Install Ingress
&lt;/h1&gt;

&lt;p&gt;We will install nginx ingress using host network on all worker nodes (DaemonSet).&lt;/p&gt;

&lt;p&gt;See this document to understand more about nginx ingress &lt;a href="https://kubernetes.github.io/ingress-nginx/deploy/baremetal" rel="noopener noreferrer"&gt;Bare-metal considerations&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Install Ingress pod
&lt;/h3&gt;

&lt;p&gt;Install using helm command below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm install --name nginx-ingress --namespace nginx-ingress stable/nginx-ingress --set controller.hostNetwork=true --set controller.kind=DaemonSet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open one of your worker node IP address in the browser. If ingress installation successful, you will see.&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Faexnlwvy1jddsc96a2f8.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Faexnlwvy1jddsc96a2f8.png" title="Install success"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Configure domain name
&lt;/h3&gt;

&lt;p&gt;Configure your dns to point your test domain name to one of your worker node.&lt;/p&gt;

&lt;p&gt;In my example, I point my route53 test dns record to my public IP.&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fmftgd7u72g64qgxhqfa2.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fmftgd7u72g64qgxhqfa2.png" title="Route53 dns"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also need to configure my router to forword port 80 and 443 to one of my worker node. Let's say 192.168.1.110.&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fbrxpkrtzukdmtzp3762i.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fbrxpkrtzukdmtzp3762i.png" title="Router config"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Deploy test application
&lt;/h3&gt;

&lt;p&gt;Run this command to deploy hello world application to the cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl run hello-world --image=gcr.io/google-samples/node-hello:1.0  --port=8080
kubectl expose deployment hello-world --type=NodePort --name=example-service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try access this application using node port &lt;code&gt;http://&amp;lt;master or worker ip&amp;gt;:&amp;lt;node port&amp;gt;&lt;/code&gt;. You should see.&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fj8fxoxk79wsikhu00q0u.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fj8fxoxk79wsikhu00q0u.png" title="Test app"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Deploy test ingress
&lt;/h3&gt;

&lt;p&gt;Run command below to install ingress for test application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Please replace &lt;strong&gt;"testingress.yourdomain.com"&lt;/strong&gt; with your real domain.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo 'apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: test-ingress
  namespace: default
  annotations:
    kubernetes.io/ingress.class: "nginx"
    kubernetes.io/tls-acme: "true"
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
  tls:
  - hosts:
    - testingress.yourdomain.com
    secretName: testingress-tls
  rules:
  - host: testingress.yourdomain.com
    http:
      paths:
      - path: /
        backend:
          # The name of your service
          serviceName: example-service
          servicePort: 8080' | kubectl apply -f -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything is ok, open url &lt;code&gt;http://testingress.yourdomain.com&lt;/code&gt; and you should see.&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F010n1txkx1i39g95i5ka.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F010n1txkx1i39g95i5ka.png" title="Test app with domain"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Install cert-manager
&lt;/h1&gt;

&lt;p&gt;We will install cert-manager to the cluster then create cluster issuer for letsencrypt. The cluster issuer will automatically detect our ingress with tls configure then try to acquire certificate and store keys in secret name "testingress-tls".&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Install cert-manager
&lt;/h3&gt;

&lt;p&gt;Run this command using helm.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm install --name cert-manager --namespace kube-system stable/cert-manager
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Create cluster issuer (use http01 protocol)
&lt;/h3&gt;

&lt;p&gt;Run this command to create cluster issuer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Please replace "&lt;a href="mailto:yourreal@email.com"&gt;yourreal@email.com&lt;/a&gt;" to your real email&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo 'apiVersion: certmanager.k8s.io/v1alpha1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
  namespace: default
spec:
  acme:
    email: yourreal@email.com
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-prod
    http01: {}' | kubectl apply -f -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run below to update cert-manager to use cluster issuer created above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm upgrade cert-manager stable/cert-manager --namespace kube-system --set ingressShim.defaultIssuerName=letsencrypt-prod --set ingressShim.defaultIssuerKind=ClusterIssuer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait for sometimes maybe an hour. Then run &lt;code&gt;kubectl get secret&lt;/code&gt;. If you see secret name "testingress-tls", this mean it works. You should be able to access &lt;code&gt;https://testingress.yourdomain.com&lt;/code&gt; successfully.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Create cluster issuer (use dns01 protocol)
&lt;/h3&gt;

&lt;p&gt;In case that step 2 with http02 protocol does not work, we will need a more advance protocol "dns01".&lt;/p&gt;

&lt;p&gt;"dns01" protocol support many cloud providers, this example will be for route53.&lt;/p&gt;

&lt;h4&gt;
  
  
  3.1 Import IAM policy
&lt;/h4&gt;

&lt;p&gt;Import this to your IAM user that you have access key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "route53:GetChange",
            "Resource": "arn:aws:route53:::change/*"
        },
        {
            "Effect": "Allow",
            "Action": "route53:ChangeResourceRecordSets",
            "Resource": "arn:aws:route53:::hostedzone/*"
        },
        {
            "Effect": "Allow",
            "Action": "route53:ListHostedZonesByName",
            "Resource": "*"
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3.2 Create AWS secret
&lt;/h4&gt;

&lt;p&gt;Create kubernetes secret using below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Replace  with your real secret key&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl create secret -n kube-system generic aws-secret --from-literal=secret-key=&amp;lt;IAM User Secret Key&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3.3 Create Cluster Issuer (DNS01)
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Replace &lt;a href="mailto:yourreal@email.com"&gt;yourreal@email.com&lt;/a&gt; with your real email&lt;br&gt;
Replace  and  and  with your real data&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo 'apiVersion: certmanager.k8s.io/v1alpha1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
  namespace: default
spec:
  acme:
    email: yourreal@email.com
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-prod
    dns01:
      providers:
      - name: aws-route53
        route53:
          hostedZoneID: &amp;lt;Route53 hosted zone ID&amp;gt;
          region: &amp;lt;Route53 region&amp;gt;
          accessKeyID: &amp;lt;IAM User Access key&amp;gt;
          secretAccessKeySecretRef:
            name: aws-secret
            key: secret-key' | kubectl apply -f -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3.4 Update test ingress to use DNS01
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Replace yourdomain.com to your real domain&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo 'apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: test-ingress
  namespace: default
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
    certmanager.k8s.io/acme-challenge-type: "dns01"
    certmanager.k8s.io/acme-dns01-provider: "aws-route53"
spec:
  tls:
  - hosts:
    - testingress.yourdomain.com
    secretName: testingress-tls
  rules:
  - host: testingress.yourdomain.com
    http:
      paths:
      - path: /
        backend:
          # The name of your service
          serviceName: example-service
          servicePort: 8080' | kubectl apply -f -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait until &lt;code&gt;kubectl get secret&lt;/code&gt; return secret name "testingress-tls", this mean it works. You should be able to access &lt;code&gt;https://testingress.yourdomain.com&lt;/code&gt; successfully.&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Flc7xmjk1ud212nr29pnd.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Flc7xmjk1ud212nr29pnd.png" title="TLS secret"&gt;&lt;/a&gt;&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fgb8f1ewrocwg91s3ro2f.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fgb8f1ewrocwg91s3ro2f.png" title="https"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;At this point, we have a kubernete cluster that we can deploy application that support https. &lt;a href="https://dev.to/pongsatt/deploy-static-front-end-application-to-kubernetes-cluster-using-jenkins-cd-and-minio-with-automatic-https-cert-44gk"&gt;Next&lt;/a&gt;, we will set up a static front-end application in the kubernetes cluster.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>ingress</category>
      <category>certmanager</category>
      <category>letsencrypt</category>
    </item>
    <item>
      <title>Setup dynamic storage class provisioner based on NFS</title>
      <dc:creator>pongsatt</dc:creator>
      <pubDate>Sat, 03 Nov 2018 06:57:24 +0000</pubDate>
      <link>https://dev.to/pongsatt/setup-dynamic-storage-class-provisioner-based-on-nfs-1d4i</link>
      <guid>https://dev.to/pongsatt/setup-dynamic-storage-class-provisioner-based-on-nfs-1d4i</guid>
      <description>&lt;p&gt;This post is a guide to setup NFS storage class in your kubernetes cluster virtual machine. &lt;/p&gt;

&lt;p&gt;It is part of series &lt;a href="https://dev.to/pongsatt/setup-your-own-kubernetes-cluster-on-vms-5ln"&gt;"Setup your own kubernetes cluster on VMs"&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In a cluster, our application can be run on any host machine so application like database will need to know where to persist the data.&lt;/p&gt;

&lt;p&gt;Kubernetes cluster supports this by using Persistent Volume (PV) and Persistent Volume Claim (PVC) which is cluster storage resource. We need to define PV with underlying actual storage then define PVC that use it, then bind PVC to an application. &lt;/p&gt;

&lt;p&gt;Kubernetes also support Storage Class in which instead of define PV manually, we can set storage class to application PVC as an annotation. Then, cluster will create a PV that match the storage class for us automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: db-claim
  annotations:
    volume.beta.kubernetes.io/storage-class: "ssdnfs"
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each cloud provider like AWS or Google has its own support for storage class that bind the actual storage with their existing cloud storage.&lt;/p&gt;

&lt;p&gt;Since our kubernetes cluster is on a VMs or bare metal, we don't have this capability out of the box.&lt;/p&gt;

&lt;p&gt;In this post, I will guide you to setup storage class based on NFS using a program named "nfs-client-provisioner".&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisite:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A kubernetes cluster on VMs or bare metal with RBAC enabled&lt;/li&gt;
&lt;li&gt;A NFS server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will create a storage class name &lt;strong&gt;ssdnfs&lt;/strong&gt; as a default storage class.&lt;br&gt;
Let's assume that we have NFS server on IP &lt;code&gt;192.168.1.119&lt;/code&gt; and export path &lt;code&gt;/export/k8sdynamic&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you follow from &lt;a href="https://dev.to/pongsatt/setup-kubernetes-cluster-using-kubeadm-hpb"&gt;previous post&lt;/a&gt;, you need to be on the master node.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Create storage class
&lt;/h3&gt;

&lt;p&gt;Run command below to define our storage class to the cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo 'apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: ssdnfs
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
provisioner: k8s/nfs' | kubectl apply -f -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Create service account and permission
&lt;/h3&gt;

&lt;p&gt;The provisioner needs permission to monitor and create PV for us so we need a service account with appropriate permission.&lt;/p&gt;

&lt;p&gt;Create service account.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo "apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner" | kubectl apply -f -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Define cluster role.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo 'kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-client-provisioner-runner
rules:
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch", "create", "delete"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["list", "watch", "create", "update", "patch"]
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]' | kubectl apply -f -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bind cluster to service account.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo 'kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: run-nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    namespace: default
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-runner
  apiGroup: rbac.authorization.k8s.io' | kubectl apply -f -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Deploy provisioner as pod
&lt;/h3&gt;

&lt;p&gt;Run command below to deploy nfs-client-provisioner application to the cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo 'kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  name: nfs-client-provisioner
spec:
  replicas: 1
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccount: nfs-client-provisioner
      containers:
        - name: nfs-client-provisioner
          image: quay.io/external_storage/nfs-client-provisioner:v3.1.0-k8s1.11
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: k8s/nfs
            - name: NFS_SERVER
              value: 192.168.1.119 # nodes will need nfs-common to access nfs protocol
            - name: NFS_PATH
              value: /export/k8sdynamic
      volumes:
        - name: nfs-client-root
          nfs:
            server: 192.168.1.119
            path: /export/k8sdynamic' | kubectl apply -f -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;kubectl get deployment nfs-client-provisioner&lt;/code&gt; and wait until you see.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fsyaibc3lyzkq198c089n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fsyaibc3lyzkq198c089n.png" title="Deployment Success" width="800" height="134"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Test our storage class
&lt;/h3&gt;

&lt;p&gt;We will create PVC that use our "ssdnfs" storage class and run a pod that use this PVC.&lt;/p&gt;

&lt;p&gt;Create PVC.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo 'kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: test-claim
  annotations:
    volume.beta.kubernetes.io/storage-class: "ssdnfs"
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Mi' | kubectl apply -f -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create test pod.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo 'kind: Pod
apiVersion: v1
metadata:
  name: test-pod
spec:
  containers:
  - name: test-pod
    image: gcr.io/google_containers/busybox:1.24
    command:
      - "/bin/sh"
    args:
      - "-c"
      - "touch /mnt/SUCCESS &amp;amp;&amp;amp; exit 0 || exit 1"
    volumeMounts:
      - name: nfs-pvc
        mountPath: "/mnt"
  restartPolicy: "Never"
  volumes:
    - name: nfs-pvc
      persistentVolumeClaim:
        claimName: test-claim' | kubectl apply -f -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;kubectl get pod test-pod&lt;/code&gt; and if you see status "completed", it means our storage class works!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F181366888dhblgacl16x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F181366888dhblgacl16x.png" title="Test Success" width="800" height="134"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;At this point, we have our own storage class that we can use for our application in our kubernetes cluster. &lt;a href="https://dev.to/pongsatt/set-up-nginx-ingress-with-letsencrypt-certificate-on-vms-or-bare-metal-kubernetes-cluster-j25"&gt;Next&lt;/a&gt;, we will setup nginx ingress with cert-manager so we can use https with our application.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>storage</category>
      <category>nfs</category>
    </item>
    <item>
      <title>Setup kubernetes cluster using kubeadm</title>
      <dc:creator>pongsatt</dc:creator>
      <pubDate>Sat, 03 Nov 2018 06:56:11 +0000</pubDate>
      <link>https://dev.to/pongsatt/setup-kubernetes-cluster-using-kubeadm-hpb</link>
      <guid>https://dev.to/pongsatt/setup-kubernetes-cluster-using-kubeadm-hpb</guid>
      <description>&lt;p&gt;From &lt;a href="https://dev.to/pongsatt/prepare-virtual-machine-using-virtualbox-and-ubuntu-558j"&gt;previous post&lt;/a&gt;, you prepare a number of VMs machine, in this post, we will install kubeadm and setup it to work together as a cluster.&lt;/p&gt;

&lt;p&gt;This is part of &lt;a href="https://dev.to/pongsatt/setup-your-own-kubernetes-cluster-on-vms-5ln"&gt;Setup your own kubernetes cluster on VMs&lt;/a&gt; series.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is an opinionated kubernetes cluster setup so it is easy enough for beginners. Meaning, something have already been chosen for you but you can change it if you want.&lt;br&gt;
This post is based on &lt;a href="https://kubernetes.io/docs/setup/independent/install-kubeadm/" rel="noopener noreferrer"&gt;this kubernetes official document&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Prerequisite:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;At least 2 Virtual machines with Ubuntu installed and connected network&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Switch to root user (All machines)
&lt;/h1&gt;

&lt;p&gt;Run below command and enter root password (password when you setup VM)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo su
&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fkwomwjpa4pay1thg5zqd.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fkwomwjpa4pay1thg5zqd.png" title="Switch to root user"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Install Docker (All machines)
&lt;/h1&gt;

&lt;p&gt;Follow these steps to install docker to all machines.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Install Docker
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Install Docker from Ubuntu's repositories:
apt-get update
apt-get install -y docker.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Configure docker
&lt;/h3&gt;

&lt;p&gt;In case you want to use your own docker private registry, follow below, otherwise follow "Without private registry below"&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; "192.168.1.105:8082" is my docker private registry.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Setup daemon.
cat &amp;gt; /etc/docker/daemon.json &amp;lt;&amp;lt;EOF
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m"
  },
  "storage-driver": "overlay2",

  "insecure-registries": [
    "192.168.1.105:8082"
  ],
  "disable-legacy-registry": true
}
EOF

mkdir -p /etc/systemd/system/docker.service.d

# Restart docker.
systemctl daemon-reload
systemctl restart docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without private registry.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Setup daemon.
cat &amp;gt; /etc/docker/daemon.json &amp;lt;&amp;lt;EOF
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m"
  },
  "storage-driver": "overlay2"
}
EOF

mkdir -p /etc/systemd/system/docker.service.d

# Restart docker.
systemctl daemon-reload
systemctl restart docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. (Optional) Install image cleanup job
&lt;/h3&gt;

&lt;p&gt;I recommend to install this job to clean up unused docker images otherwise they will consume all your disk.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -d --restart=always \
  -v /var/run/docker.sock:/var/run/docker.sock:rw \
  -v /var/lib/docker:/var/lib/docker:rw \
  -e "CLEAN_PERIOD=86400" \
  meltwater/docker-cleanup:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Check if everything alright
&lt;/h3&gt;

&lt;p&gt;Run below command to see if everything is ok.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see something like below on all machines and you're good to proceed.&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fxy6w99f0qe9jp97jtyt5.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fxy6w99f0qe9jp97jtyt5.png" title="Docker ps"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F8swpt8056k7eek65qyco.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F8swpt8056k7eek65qyco.png" title="Docker ps"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Install Kubeadm (All machines)
&lt;/h1&gt;

&lt;p&gt;Next step is to install kubernetes cluster tool.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Disable swap
&lt;/h3&gt;

&lt;p&gt;This is a prerequisite for kubeadm.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# disable temporarily
swapoff -a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To disable permanently, edit /etc/fstab and command swap line out and save.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vi /etc/fstab

&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fbisgoywyxdq5gg0w6gal.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fbisgoywyxdq5gg0w6gal.png" title="Disable swap"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note for vi newby:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;move cursor to the beginning of last line&lt;/li&gt;
&lt;li&gt;type 'i' to insert&lt;/li&gt;
&lt;li&gt;type '#' then 'esc'&lt;/li&gt;
&lt;li&gt;type ':wq' then enter to save&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Run below commands on all machines
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apt-get update &amp;amp;&amp;amp; apt-get install -y apt-transport-https curl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
cat &amp;lt;&amp;lt;EOF &amp;gt;/etc/apt/sources.list.d/kubernetes.list
deb http://apt.kubernetes.io/ kubernetes-xenial main
EOF
apt-get update
apt-get install -y kubelet kubeadm kubectl nfs-common
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apt-mark hold kubelet kubeadm kubectl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These commands will install:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;kubelet : kubernetes node runtime&lt;/li&gt;
&lt;li&gt;kubeadm : kubernetes cluster setup tool&lt;/li&gt;
&lt;li&gt;kubectl : kubernetes cluster command line interface&lt;/li&gt;
&lt;li&gt;nfs-common: nfs client library to be able to connect to NFS server&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Setup cluster (Master Node only)
&lt;/h1&gt;

&lt;p&gt;This step, we will initialize master node by running command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# init master
kubeadm init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; Run on master node only&lt;/p&gt;

&lt;p&gt;You will need to wait for sometimes depending on your internet connection.&lt;/p&gt;

&lt;p&gt;If everything is ok, you will see.&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fn0hu2jrhy2cmkjz8yo9p.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fn0hu2jrhy2cmkjz8yo9p.png" title="kubeadm init"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;br&gt;
Please copy the last line. It will be used in the next step to join worker node.&lt;/p&gt;
&lt;h1&gt;
  
  
  Setup cluster (Worker Node only)
&lt;/h1&gt;

&lt;p&gt;Run join command copied from previous step on all the worker nodes.&lt;/p&gt;

&lt;p&gt;Here is an example (Do not use this command, it won't work for you)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubeadm join 192.168.1.109:6443 --token srzyez.wjnqsmt2gcohxtp4 --discovery-token-ca-cert-hash sha256:ac8d21e46aeaba3664c4f6060072de03d906d0032f9731b021eeb8d54a876e35
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On worker node, if join command succeed, you will see.&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fe097gdwx4i58rssq8x3p.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fe097gdwx4i58rssq8x3p.png" title="Worker join result"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Post install (Master node only)
&lt;/h1&gt;

&lt;h3&gt;
  
  
  1. Setup cluster connection configuration
&lt;/h3&gt;

&lt;p&gt;Switch back to normal user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run below command and enter root password.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# setup kube config
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Install Pod network (weavenet)
&lt;/h3&gt;

&lt;p&gt;A kubernetes cluster needs pod network. To install weavenet (one of several pod network providers), run command on master node as below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo sysctl net.bridge.bridge-nf-call-iptables=1
kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 | tr -d '\n')"
&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fgb116tqc17jfsw9vwy4a.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fgb116tqc17jfsw9vwy4a.png" title="Pod network installed"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Check the result
&lt;/h3&gt;

&lt;p&gt;On master node, run command below.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;If you see something like below, congratulation! you have your cluster.&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fj51gqqdto5a8wb0j8go6.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fj51gqqdto5a8wb0j8go6.png" title="Get nodes result"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Install Dashboard (Optional)
&lt;/h1&gt;

&lt;p&gt;To be able to see the overall picture of your cluster, you need a dashboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Install dashboard
&lt;/h3&gt;

&lt;p&gt;On master node, run below command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/recommended/kubernetes-dashboard.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Setup permission to access dashboard
&lt;/h3&gt;

&lt;p&gt;For simplify thing, we will grant admin permission to anyone access dashboard. You won't do this in the actual cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo "apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: kubernetes-dashboard
  labels:
    k8s-app: kubernetes-dashboard
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: kubernetes-dashboard
  namespace: kube-system" | kubectl apply -f -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Run proxy command
&lt;/h3&gt;

&lt;p&gt;Open a proxy so that the cluster can be accessed from outside.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl proxy --address='0.0.0.0' --accept-hosts='.*'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Open dashboard UI
&lt;/h3&gt;

&lt;p&gt;Open url &lt;code&gt;https://&amp;lt;your master node ip&amp;gt;:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You should see login page.&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fqwqtpnfl244pnb5svlpw.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fqwqtpnfl244pnb5svlpw.png" title="Dashboard login"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click "Skip" button and you will see overview page.&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fuukarje7wh7y56vijbxk.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fuukarje7wh7y56vijbxk.png" title="Dashboard overview"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Congratulation! You've got a dashboard to see and manage your workload.&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;We've got a kubernetes cluster running on VMs. &lt;a href="https://dev.to/pongsatt/setup-dynamic-storage-class-provisioner-based-on-nfs-1d4i"&gt;Next step&lt;/a&gt; will setup cluster storage so we can build application that store something.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>kubeadm</category>
      <category>virtualbox</category>
    </item>
    <item>
      <title>Prepare Virtual Machine using Virtualbox and Ubuntu</title>
      <dc:creator>pongsatt</dc:creator>
      <pubDate>Sat, 03 Nov 2018 06:52:42 +0000</pubDate>
      <link>https://dev.to/pongsatt/prepare-virtual-machine-using-virtualbox-and-ubuntu-558j</link>
      <guid>https://dev.to/pongsatt/prepare-virtual-machine-using-virtualbox-and-ubuntu-558j</guid>
      <description>&lt;p&gt;This post will guide you to create 4 virtual machine based on Ubuntu Server. One designated to be kubernetes master node and the other three as worker nodes.&lt;/p&gt;

&lt;p&gt;This is part of &lt;a href="https://dev.to/pongsatt/setup-your-own-kubernetes-cluster-on-vms-5ln"&gt;Setup your own kubernetes cluster on VMs&lt;/a&gt; series.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This guide is based on Windows but since it's a virtual machine, others OS shouldn't be different&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Prerequisite:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Install &lt;a href="https://www.virtualbox.org/wiki/Downloads" rel="noopener noreferrer"&gt;VirtualBox&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Download &lt;a href="https://www.virtualbox.org/wiki/Linux_Downloads" rel="noopener noreferrer"&gt;Ubuntu 16.04 Image for VirtualBox&lt;/a&gt; or &lt;a href="https://download.virtualbox.org/virtualbox/5.2.20/virtualbox-5.2_5.2.20-125813~Ubuntu~xenial_amd64.deb" rel="noopener noreferrer"&gt;64bit Direct link&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Available 2GB of RAM per VM (4 nodes will need at 8GB). You can reduce number of nodes depending on your available RAM&lt;/li&gt;
&lt;li&gt;10-30GB of disk space per VM (4 nodes will need around 100GB)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  In the end
&lt;/h2&gt;

&lt;p&gt;You will get something like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fo4r7iptl0xos4cxizdgi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fo4r7iptl0xos4cxizdgi.png" title="In the end" alt="At the end" width="800" height="468"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  1. Create Virtual Machine
&lt;/h1&gt;

&lt;p&gt;We will create 4 virtual machines. 1 machine is for kubernetes master and the reset for worker node. They're all mostly the same except hostname. RAM and cpu can be adjusted based on your need. In theory, master node will need less resource comparing to worker node.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.1. Create New Virtual Machine
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Name:&lt;/em&gt; k8smaster&lt;br&gt;
   &lt;em&gt;Type:&lt;/em&gt; linux&lt;br&gt;
   &lt;em&gt;Memory:&lt;/em&gt; 2048MB&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fms7949bf014nnmhx6mbk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fms7949bf014nnmhx6mbk.png" title="Create1" alt="Step1" width="800" height="718"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1.2. Choose harddisk size
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Size:&lt;/em&gt; 10GB&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F37eypv0vb11ns294eqei.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F37eypv0vb11ns294eqei.png" title="Create2" alt="Step2" width="800" height="723"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F09wude0rg0qg58hg9iu9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F09wude0rg0qg58hg9iu9.png" title="Create3" alt="Done" width="800" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  2. Prepare VM before install OS
&lt;/h1&gt;

&lt;h3&gt;
  
  
  2.1. Setup network
&lt;/h3&gt;

&lt;p&gt;We will use bridge network so we have the real network ip address that can talk to the outside world.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click "Settings" then "Network".&lt;/li&gt;
&lt;li&gt;Select "Attached to" as "Bridged Adapter"&lt;/li&gt;
&lt;li&gt;Click "Advance"&lt;/li&gt;
&lt;li&gt;Write down "MAC Address" to reserve IP address so that your IP will not change every time the VM restarted&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fm5nxilmrzyber68rftzj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fm5nxilmrzyber68rftzj.png" title="Network setup" width="800" height="549"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2.2. Setup Ubuntu image to be installed
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Click "Settings" then "Storage"&lt;/li&gt;
&lt;li&gt;Click "Adds optical drive" at the top right corner&lt;/li&gt;
&lt;li&gt;Select os image (.iso) you downloaded&lt;/li&gt;
&lt;li&gt;Click OK&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fsc27y1ps1m7kikugv5wb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fsc27y1ps1m7kikugv5wb.png" title="Storage setup" width="800" height="549"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  3. Create the rest VMs
&lt;/h1&gt;

&lt;p&gt;Repeat step 1 and 2 with below information&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name: kubenode1, kubenode2, kubenode3&lt;/li&gt;
&lt;li&gt;RAM: 2048GB (or more)&lt;/li&gt;
&lt;li&gt;Disk: 32GB (or less)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F4fddk9wysfovzfu9fkew.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F4fddk9wysfovzfu9fkew.png" title="All nodes" width="800" height="468"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  4. Setup Ubuntu
&lt;/h1&gt;

&lt;h3&gt;
  
  
  4.1 Start VM (kubemaster)
&lt;/h3&gt;

&lt;p&gt;Click "Start" button icon.&lt;/p&gt;

&lt;p&gt;Ubuntu setup screen will be shown as below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F1j12qxi66tkk1x2wjxo8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F1j12qxi66tkk1x2wjxo8.png" title="Setup1" width="800" height="687"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select "Install Ubuntu Server&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fd0tsq1tvwkwaavliki2a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fd0tsq1tvwkwaavliki2a.png" title="Setup2" width="800" height="687"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2 Set Hostname
&lt;/h3&gt;

&lt;p&gt;Follow instructions until this screen and fill "kubemaster" as hostname.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F4hkuzvn3c7n10blwpopc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F4hkuzvn3c7n10blwpopc.png" title="Hostname" width="800" height="670"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4.3 Set user name
&lt;/h3&gt;

&lt;p&gt;Fill in "Full name", "username" and "password" as needed. Please make note these information.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fb5okajzglphrir9vylra.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fb5okajzglphrir9vylra.png" title="Username" width="800" height="670"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4.4 Other information
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;"Encrypt your home directory?" : No&lt;/li&gt;
&lt;li&gt;"Partition disk" : "Guided - use entire disk"&lt;/li&gt;
&lt;li&gt;"Write the changes to disks?" : Yes&lt;/li&gt;
&lt;li&gt;"HTTP proxy information" : leave blank (if you are outside company proxy)&lt;/li&gt;
&lt;li&gt;"How do you want to manage upgrades on this system?" : No automatic updates&lt;/li&gt;
&lt;li&gt;"Software selection" : select "OpenSSH server" (This is required if you want you connect to your server remotely using ssh command)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fcr9qudcufjnja4r4p52f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fcr9qudcufjnja4r4p52f.png" title="Software" width="800" height="670"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4.5 Install GRUB boot loader
&lt;/h3&gt;

&lt;p&gt;Select "Yes" then "Finish Installation"&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fgykthxw2j1pdva1up3ni.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fgykthxw2j1pdva1up3ni.png" title="GRUB" width="800" height="670"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  5. Setup Ubuntu for the rest VMs
&lt;/h1&gt;

&lt;p&gt;Repeat step 4 for "kubenode1" to "kubenode3"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Don't forget to change hostname to "kubenode1" to "kubenode3" in step 4.2&lt;/li&gt;
&lt;li&gt;Username and password can be the same for all VMs&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;Now, you have all the VMs with network and ready to setup kubernetes cluster in the &lt;a href="https://dev.to/pongsatt/setup-kubernetes-cluster-using-kubeadm-hpb"&gt;next step&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>virtualbox</category>
      <category>ubuntu</category>
    </item>
  </channel>
</rss>
