Sunday, 9 June 2019

Flannel vs Calico : A battle of L2 vs L3 based networking

We discuss today the networking in container world and primarily in context of K8s . We are not covering the policies and isolation part , but only how L2 and L3 play a role in packet flows.

Flannel is an overlay network mechanism where as Calico is basically a pure L3 play.

Flannel works by using a vxlan device in conjunction with a software switch like linux bridge or ovs.

Container A when tries to reach container B on different host the traffic is pushed to the bridge on host A via the VETH pair. The bridge then based on ARP tries to get the mac of container B. Since container B is not on the host the traffic by bridge is forwarded at L2 to the vxlan device (software TAP device) which then allows flannel daemon software to capture those packets and then wrap then into a L3 packet for transport over a physical network using UDP. Also vxlan tagging is added to the packet to isolate them between tenants.
Flannel shown diagrammatically


In case of Calico, the approach is little different. Calico works at Layer 3 and depends on Linux routing for moving the packets.
Calico injects a routing rule inside the container for gateway at this IP 169.254.1.1.
default via 169.254.1.1 dev eth0
169.254.1.1 dev eth0 scope link
What this means is that any traffic from the container first tries to go to the default gateway IP. Since the default gateway IP is reachable at eth0 , the ARP request is sent to eth0 for determining the mac address for gateway IP.
The trick here is the arp proxy configured at the veth device on host side. This arp proxy responds back with its mac for the ARP request for 169.254.1.1.
Post this resolution the packets are sent to the veth device with source IP of container and destination IP of target container. From here on the L3 routing of the host takes effect which knows how to route for the destination container IP.
The routes amongst the hosts are synchronized via the BGP protocol. There is a BGP client (Bird) running on each host which makes sure each host has the updated routes.
So here you can see in Calico solution, we got rid of software bridges as well as preserved the source IP.
Diagrammatically the flow is shown below

Also the overlay complexity is out of the picture and it’s a pure L3 solution just based on the principles of how the internet works. Since we make use of routing principles rather then L2 broadcast domains, the need of vlan is eliminated. Instead for tenant specific network flows Calico resorts to iptables based mechanism.
So if we just try to compare how say a bridge based communication happens vs a pure L3 communication, the difference is that in case of bridge the bridge device IP acts as the gateway for containers and so the next hop for any traffic not within same broadcast domain is directed to the bridge device. This allows the L3 on linux kernel on the host to apply the routing (the routing rules are configured to forward the packets to the vm on which destination container resides) or they are forwarded to a tap device to give opportunity to tunnel the packets via GRE/vxlan.
On the contrary the Calico approach relies on proxy ARP mechanism to transfer the packet to the veth counterpart device on host side and again applying the routing to take traffic out. So if we analyse this carefully, technically the bridge is replaced with proxy ARP and route synchronization happens over BGP.
For more information on Calico you can take a look at https://www.projectcalico.org/
In essence packets from vm or containers can use one of the following mechanisms to communicate with containers/vms on other hosts
1.     Use overlay like GRE/VXLAN
2.     use NAT to send packets to remote host
3.     use Calico like mechanism with pure L3 routing without having any NAT and bridges. This allows to preserve source IP and security policies ingress can be applied adequately based on source IPs

Thursday, 18 April 2019

Sample Jenkinsfile to create Jenkins Pipeline

#!groovy?

// properties([[$class: 'BuildDiscarderProperty', strategy: [$class: 'LogRotator', numToKeepStr: '3']]])

properties(
  [
    office365ConnectorWebhooks([[notifyBackToNormal: true, notifyFailure: true, notifyRepeatedFailure: true, notifySuccess: true, notifyUnstable: true, url: 'https://outlook.office.com/webhook/e20e3b8e-ef2b-44a8-8ed7-70e44725e4f8@e0793d39-0939-496d-b129-198edd916feb/IncomingWebhook/8584f894ef9c4c759c03261f6f3ac987/b41687d2-b80a-40bc-af19-b2b7756570f7']]),buildDiscarder(logRotator(numToKeepStr: '3'))
  ]
)

node(){
    compile()
    // deploy()
 codequality()
    dockerBuild()
 propertiesFile()
    deploy2k8s()
}

def compile(){
 stage('CleanUp') {
  cleanWs()
 }
    stage('Build') {
            checkout scm
            def pom_version = version()
            //currentBuild.displayName =  "${pom_version}-${env.BUILD_NUMBER}"
            //mvn "versions:set -DnewVersion=${currentBuild.displayName}"
            mvn "clean compile test package"  
    }
}


def codequality(){
    pom = readMavenPom file: 'pom.xml'
    def POM_VERSION = pom.version
    def POM_ARTIFACTID = pom.artifactId
  stage('SonarQube analysis') {
    // requires SonarQube Scanner 2.8+
    def scannerHome = tool 'sonar-scanner';
    withSonarQubeEnv('ipssonarqube') {
      sh "${scannerHome}/bin/sonar-scanner -Dsonar.projectKey=${POM_ARTIFACTID} -Dsonar.login=admin -Dsonar.password=admin -Dsonar.java.binaries=target/classes -Dsonar.java.libraries=src/"
    }
 
}

 stage("Quality Gate"){
          timeout(time: 2, unit: 'MINUTES') {
              def qg = waitForQualityGate()
              if (qg.status != 'OK') {
                  error "Pipeline aborted due to quality gate failure: ${qg.status}"
              }
          }
}

}

def deploy(){
    stage("Deploy to Nexus"){
    mvn "deploy  -DaltDeploymentRepository=nexus::default::https://repo/"
    }
}

def dockerBuild(){
    pom = readMavenPom file: 'pom.xml'
    def POM_VERSION = pom.version
    def POM_ARTIFACTID = pom.artifactId
    stage('Docker Image'){
        docker_image = docker.build("dockerhub/test/${POM_ARTIFACTID}:${POM_VERSION}","--build-arg POM_VERSION=${POM_VERSION} --build-arg POM_ARTIFACTID=${POM_ARTIFACTID} .")
 docker_image.push()
 sh "sudo docker system prune -f"
 }
}

def propertiesFile(){
// This section will update the properties file to create Kubernetes configmap
 sh "sed -i 's/{{ KAFKA_BROKER }}/localhost:9092/g' config.sample/application.properties"
 sh "sed -i 's/{{ KAFKA_TOPIC }}/patient/g' config.sample/application.properties"
 sh "sed -i 's/{{ KAFKA_GROUPID }}/poa-gp/g' config.sample/application.properties"
}

def deploy2k8s(){
    pom = readMavenPom file: 'pom.xml'
    def POM_VERSION = pom.version
    def POM_ARTIFACTID = pom.artifactId
   
    stage('Deploy to K8S'){
    
    environment {
    POM_VERSION=${POM_VERSION}
    POM_ARTIFACTID=${POM_ARTIFACTID}
    }
        sh "cp ~/.kube/config config"
     sh "echo '$POM_VERSION and $POM_ARTIFACTID'"
        //sh "kubectl delete -f *.yaml --kubeconfig=config"
  sh "sed -i 's/POM_VERSION/$POM_VERSION/g' app.yaml"
  sh "sed -i 's/POM_ARTIFACTID/$POM_ARTIFACTID/g' app.yaml"
     sh "kubectl delete cm $POM_ARTIFACTID --kubeconfig=config"
  sh "kubectl delete -f app.yaml --kubeconfig=config"
  sh "kubectl create cm $POM_ARTIFACTID --from-file=config.sample/application.properties --kubeconfig=config"
     sh "kubectl apply -f app.yaml --kubeconfig=config" 
    }
}

def mvn(String goals) {
    def mvnHome = tool "maven-3.5.0"
    def javaHome = tool "jdk8"

    withEnv(["JAVA_HOME=${javaHome}", "PATH+MAVEN=${mvnHome}/bin"]) {
        sh "mvn ${goals}"
    }
}

def version() {
    pom = readMavenPom file: 'pom.xml'
    return pom.version
}

Sunday, 3 March 2019

Break and Continue statement in shell script


All statement inside the loop executed as long as some condition are true.

If break placed inside the loop, when loop reach the break statement it will terminated out from the loop.
If continue placed inside the loop, when loop reach the continue statement it will not execute next lines of the loop and it will go to the next iteration.

script for break:
#!/bin/bash
x=0
while [ $x -le 5 ]
do
    echo "Before break : $x"
    x=`expr $x + 1`
    break
    echo "After breakn : $x"
done
echo "While loop finished"
output:
Before break : 0
While loop finished
script for continue:
#!/bin/bash
x=0
while [ $x -le 5 ]
do
    echo "Before continue : $x"
    x=`expr $x + 1`
    continue
    echo "After continue : $x"
done
echo "While loop finished"
    
output
Before continue : 0
Before continue : 1
Before continue : 2
Before continue : 3
Before continue : 4
Before continue : 5
While loop finished
script without break and continue:
#!/bin/bash
x=0
while [ $x -le 5 ]
do
    echo "Before operation : $x"
    x=`expr $x + 1`

    echo "After operation : $x"
done
echo "While loop finished"
   
output:
Before operation : 0
After operation : 1
Before operation : 1
After operation : 2
Before operation : 2
After operation : 3
Before operation : 3
After operation : 4
Before operation : 4
After operation : 5
Before operation : 5
After operation : 6
While loop finished