Git: Push a new repository

create a new repository on the command line

echo "# helm-nexus" >>
git init
git add
git commit -m "first commit"
git remote add origin{username}/helm-nexus.git
git push -u origin master

…or push an existing repository from the command line

git remote add origin{username}/helm-nexus.git
git push -u origin master

Chef: Local Cookbook Development



  1. Execute the command as follows:
    chef generate cookbook test_cookbook
    cd test_cookbook
    Take a look at some of the default files like, spec-helper.rb, default.rb file
  2. We will now create a generator that we will use to create cookbook with our customization:
    mkdir generator
    chef generate generator generator/lcd_origin
    cd generator/lcd_origin/templates/default
    vi insert text of your choice. Save and close.
  3. Edit kitchen.yml.erb change the contents to match the following, as we will be using docker driver and centos 7.2 image:
      name: docker
      privileged: true
      use_sudo: false
      name: chef_zero
      # You may wish to disable always updating cookbooks in CI or other testing environments.
      # For example:
      #   always_update_cookbooks: <%%= !ENV['CI'] %>
      always_update_cookbooks: true
      name: inspec
      - name: centos-7.2
          run_command: /usr/lib/systemd/systemd
      - name: default
          - recipe[<%= cookbook_name %>::default]
            - test/smoke/default
    <span 				data-mce-type="bookmark" 				id="mce_SELREST_start" 				data-mce-style="overflow:hidden;line-height:0" 				style="overflow:hidden;line-height:0" 			></span>

    Make sure you are not adding any tabs as they are invalid in ymls.

  4. Edit ../../files/default/spec_helper.rb Add the following lines:
    RSpec.configure do |config|
      config.platform = 'centos'
      config.version = '7.2.1511'
    end<span 				data-mce-type="bookmark" 				id="mce_SELREST_start" 				data-mce-style="overflow:hidden;line-height:0" 				style="overflow:hidden;line-height:0" 			></span>
  5. So to tell chef to use our generator when generating cookbooks:
    goto the lcd_origin directory
    mkdir ~/.chef
    vi ~/.chef/config.rb Add the contents as below:

    cookbook_path ['~/chef/cookbooks']
    local_mode true
    if File.basename($PROGRAM_NAME).eql?('chef') && ARGV[0].eql?('generate')
      chefdk.generator.license = "all_rights"
      chefdk.generator.copyright_holder = "Student Name"
      chefdk.generator_cookbook = "/root/cookbooks/lcd_origin" = ""
      chefdk.generator_cookbook = "~/generator/lcd_origin"
    <span 				data-mce-type="bookmark" 				id="mce_SELREST_start" 				data-mce-style="overflow:hidden;line-height:0" 				style="overflow:hidden;line-height:0" 			></span>

    Create directory chef/cookbooks
    mkdir -p ~/chef/cookbooks
    And then generate a cookbook
    cd ~/chef
    chef generate cookbook cookbooks/lcd_web
    If you check the README vi cookbooks/lcd_web/ You will see the text we added previously.
    We can generate attributes by following command:
    chef generate attribute cookbooks/lcd_web default
    We can genrate recipe by:
    chef generate recipe cookbooks/lcd_web users

  6. These attributes can be checked here in lcd_origin/attributes/default.rb
    The recipe can be checked with lcd_origin/recipes/users.rb

Test Driven Development




    1. You can check the chefspec default file  spec/unit/recipes/default_spec.rb
    2. Now let’s run the default_spec
      chef exec rspec
      If you get any warning about platform and platform_version then change the following in your code:
      runner = ‘centos’, version: ‘7.2.1511’)
      For some of you the platform and the platform_version might be already added. It might be ubuntu and 16.04 change it to centos and 7.2.1511 resp.
      Then execute the rspec again. This will give an output similar to below:
      Finished in 0.48374 seconds (files took 1.83 seconds to load)
      2 examples, 0 failures
    3. Now change the default_spec.rb to match the following to check if httpd package is installed or not:
      # Cookbook:: lcd_web
      # Spec:: default
      # Copyright:: 2017, Student Name, All Rights Reserved.
      require 'spec_helper'
      describe 'lcd_web::default' do
        context 'CentOS' do
          let(:chef_run) do
            # for a complete list of available platforms and versions see:
            runner = 'centos', version: '7.2.1511')
          it 'converges successfully' do
            expect { chef_run }.to_not raise_error
          it 'installs httpd' do
            expect(chef_run).to install_package('httpd')

      Then run chef exec rspec You will get an error as below:



      1) lcd_web::default CentOS installs httpd
      Failure/Error: expect(chef_run).to install_package(‘httpd’)

      expected “package[httpd]” with action :install to be in Chef run. Other p ackage resources:

      # ./spec/unit/recipes/default_spec.rb:23:in `block (3 levels) in ‘

      Finished in 0.63225 seconds (files took 1.68 seconds to load)
      3 examples, 1 failure

      Failed examples:

      rspec ./spec/unit/recipes/default_spec.rb:22 # lcd_web::default CentOS installs httpd

      It failed because there was no httpd package present in the system because our cookbook doesn’t do so. Let’s add httpd installation to our default recipe.

      package ‘httpd’ do
      As the default action is install we do not need to specify anything.
      Now let’s execute the rspec: chef exec rspec
      Now the output will be as follows:

      Finished in 0.73318 seconds (files took 1.67 seconds to load)
      3 examples, 0 failures

    4. Add the following below installs httpd task:
          it 'enables the httpd service' do
            expect(chef_run).to enable_service('httpd')
          it 'starts the service' do
            expect(chef_run).to start_service('httpd')
      Add the following in default.rb recipe:
      service 'httpd' do
        action [:start, :enable]
      <span 				data-mce-type="bookmark" 				id="mce_SELREST_start" 				data-mce-style="overflow:hidden;line-height:0" 				style="overflow:hidden;line-height:0" 			></span>

      Then run: chef exec rspec

    5. Now what if I add something in the default recipe that might break my cookbook:
      add the line in default.rb
      include_recipe ‘something’
      Then run rspec you will get error as below:
      Failure/Error: runner.converge(described_recipe)Chef::Exceptions::CookbookNotFound:
      Cookbook something not found. If you’re loading something from another cookbook, make sure you configure the dependency in your metadata
      # /tmp/chefspec20170918-4764-5svx8rfile_cache_path/cookbooks/lcd_web/recipes/default.rb:7:in `from_file’
      # ./spec/unit/recipes/default_spec.rb:15:in `block (3 levels) in ‘
      # ./spec/unit/recipes/default_spec.rb:31:in `block (3 levels) in ‘Finished in 0.81827 seconds (files took 1.71 seconds to load)
      5 examples, 4 failuresFailed examples:rspec ./spec/unit/recipes/default_spec.rb:18 # lcd_web::default CentOS converges successfully
      rspec ./spec/unit/recipes/default_spec.rb:22 # lcd_web::default CentOS installs httpd
      rspec ./spec/unit/recipes/default_spec.rb:26 # lcd_web::default CentOS enables the httpd service
      rspec ./spec/unit/recipes/default_spec.rb:30 # lcd_web::default CentOS starts the service

So you can see the test will fail if the cookbook dependency failes as well. You can remove the include_recipe line and re execute the rspec.

Test Kitchen Configuration


  1. Edit .kitchen.yml to match the following:
      name: docker
      privileged: true
      use_sudo: false
      name: chef_zero
      # You may wish to disable always updating cookbooks in CI or other testing environments.
      # For example:
      #   always_update_cookbooks: <%= !ENV['CI'] %>
      always_update_cookbooks: true
      name: inspec
      - name: centos-7.2
      - name: centos-6.8
      - name: dev
          run_command: /usr/sbin/init
          - recipe[la_java::default]
        attributes: { 'java': { 'jdk_version': '7' } }
            - 8080:80
        excludes: centos-6.8
      - name: prod
          run_command: /sbin/init
          - recipe[la_java::default]
        attributes: { 'java': { 'jdk_version': '6' } }
            - 8081:80
        includes: centos-6.8
    <span 				data-mce-type="bookmark" 				id="mce_SELREST_start" 				data-mce-style="overflow:hidden;line-height:0" 				style="overflow:hidden;line-height:0" 			></span>

    Execute the command kitchen list
    If you get error as follows:
    >>>>>> ——Exception——-
    >>>>>> Class: Kitchen::ClientError
    >>>>>> Message: Could not load the ‘docker’ driver from the load path. Please en sure that your driver is installed as a gem or included in your Gemfile if using Bundler.
    >>>>>> ———————-
    >>>>>> Please see .kitchen/logs/kitchen.log for more details
    >>>>>> Also try running `kitchen diagnose –all` for configuration

    Then install the chef gem kitchen-docker with:  chef gem install kitchen-docker
    Then the kitchen list output will be as follows:
    [root@arati7111 lcd_web]# kitchen list
    Instance Driver Provisioner Verifier Transport Last Action Last Error
    dev-centos-72 Docker ChefZero Inspec Ssh
    prod-centos-68 Docker ChefZero Inspec Ssh
    Then run: kitchen converge
    Then kitchen verify
    Thus java 7 will be installed in centos 7 and java 6 on centos 6

  2. If you comment the includes and excludes statements from .kitchen.yml then we will have 4 test cases.
  3. You can download the java cookbook with
    knife cookbook site download
    and add it to the run_list

Using Test Kitchen


Execute the following commands

  1. mkdir test_kitchen
  2. cd test_kitchen
  3. kitchen init
    If you get the following error while executing kitchen init:Successfully installed kitchen-vagrant-1.2.1
    Fetching: mixlib-install-3.6.0.gem (100%)
    Successfully installed mixlib-install-3.6.0
    Fetching: ffi-1.9.18.gem (100%)
    Building native extensions. This could take a while…
    ERROR: Error installing kitchen-vagrant:
    ERROR: Failed to build gem native extension./usr/bin/ruby extconf.rb
    mkmf.rb can’t find header files for ruby at /usr/share/include/ruby.h

Gem files will remain installed in /home/user/.gem/ruby/gems/ffi-1.9.18 for inspection.
Results logged to /home/user/.gem/ruby/gems/ffi-1.9.18/ext/ffi_c/gem_make.out

Then execute the kitchen init command with sudo rights

  • If even then the issue is not resolved execute the following command:
    chef gem list kitchen-vagrant
    Then you will have output as follows:  kitchen-vagrant (1.2.1)
    chef gem uninstall kitchen-vagrant -v 1.2.1
    Then rerun kitchen init with sudo rights
  • chef generate cookbook my_cookbook
    You can see that the test/integration/default directory
    In the cookbook you can see the test/smoke/default directory
  • Execute kitchen list
    If you get an error as follows:
    >>>>>> ——Exception——-
    >>>>>> Class: Kitchen::UserError
    >>>>>> Message: Vagrant 1.1.0 or higher is not installed. Please download a package from
    >>>>>> ———————-
    That means you will have to install vagrant. you can use the following command:
    rpm -ivh again execute kitchen list  you will get an output as below:
    Instance Driver Provisioner Verifier Transport Last Action Last Error
    default-ubuntu-1404 Vagrant ChefSolo Busser Ssh
    default-centos-72 Vagrant ChefSolo Busser Ssh
  • Execute kitchen create
    You will get an error as follows: The provider ‘virtualbox’ that was requested to back the machine ‘default’
    You will need to install virtualbox first: Follow this tutorial for the same:
    If you are not able to install vbox then just change the driver name from vagrant to docker in .kitchen.yml

Then execute kitchen converge
Then execute kitchen setup
Then you can execute kitchen destroy to delete the created instances


Create a cookbook with chef generate cookbook lcd_web. Edit the following file test/smoke/default/default_test.rb. Add the following lines
[ ‘net-tools’, ‘httpd’ ].each do |pkg|
describe package(pkg) do
it { should be_installed }

Then execute kitchen verify it will fail the test as below:

System Package
∅ net-tools should be installed
expected that `System Package net-tools` is installed
System Package
∅ httpd should be installed
expected that `System Package httpd` is installed

Now lets add the chef code to install these packages
Add the following lines in recipes/default.rb
[ ‘net-tools’, ‘httpd’].each do |pkg|
package pkg do
action :install

Then execute kitchen converge
Then run kitchen verify
The output should be somthing as below:
System Package
✔ net-tools should be installed
System Package
✔ httpd should be installed

Static Code Analysis

foodcritic -l
FC001: Use strings in preference to symbols to access node attributes
FC002: Avoid string interpolation where not required
FC004: Use a service resource to start and stop services
FC005: Avoid repetition of resource declarations
FC006: Mode should be quoted or fully specified when setting file permissions
FC007: Ensure recipe dependencies are reflected in cookbook metadata
FC008: Generated cookbook metadata needs updating
FC009: Resource attribute not recognised
FC010: Invalid search syntax
FC011: Missing README in markdown format
FC012: Use Markdown for README rather than RDoc
FC013: Use file_cache_path rather than hard-coding tmp paths
FC014: Consider extracting long ruby_block to library
FC015: Consider converting definition to a Custom Resource
FC016: LWRP does not declare a default action
FC017: LWRP does not notify when updated
FC018: LWRP uses deprecated notification syntax

Now cd to lcd_web cookbook
execute: foodcritic .
output: Checking 2 files
FC008: Generated cookbook metadata needs updating: ./metadata.rb:3
FC064: Ensure issues_url is set in metadata: ./metadata.rb:1
FC065: Ensure source_url is set in metadata: ./metadata.rb:1
FC067: Ensure at least one platform supported in metadata: ./metadata.rb:1
FC078: Ensure cookbook shared under an OSI-approved open source license: ./metadata.rb:1

Lets check our metadata.rb file
For error FC067 add the line in metadata.rb: supports ‘centos’
The other errors are not of our concern so we will exclude them with:
foodcritic . -t ~FC008 -t ~FC064 -t ~FC065 -t ~FC078
There should be no errors.

Now we want to persist these exclusion so we will create .foodcritic file:
vi .foodcritic and add the following lines:




Save and close. Then execute foodcritic .

Lets have a look at rubocop. To install rubocop execute gem install rubocop

You can have a look at it’s help with command rubocop –help

If you run rubocop in lcd_we cookbook you might get output as follows:

init_test/lcd_web/recipes/default.rb:7:2: C: Space inside square brackets detected.
[ ‘net-tools’, ‘httpd’].each do |pkg|
bin/mixlib-install:11:11: C: Prefer single-quoted strings when you don’t need string interpolation or special symbols.
version = “>= 0”
bin/mixlib-install:15:32: C: Prefer single-quoted strings when you don’t need string interpolation or special symbols.
str = str.dup.force_encoding(“BINARY”) if str.respond_to? :force_encoding
bin/mixlib-install:17:15: C: Avoid the use of Perl-style backrefs.
version = $1

32 files inspected, 53 offenses detected

You might have errors like “line too long” etc. By default the line length specified in 80, Now let’s see if we can override that setting.
We will be needing todo file for that let’s generate it by the command:
rubocop –auto-gen-config
Then edit the file .rubocop.yml and add the line: inherit_from: .rubocop_todo.yml
Then execute rubocop you will see that all of our offenses have been masked.
Let’s have a look at the .rubocop_todo.yml file.
Now in the code you will see code blocks like follows:

# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: auto_detection, squiggly, active_support, powerpack, unindent
    - 'generator/lcd_origin/recipes/recipe.rb'

This denotes that there i an indentation issue with recipe.rb file.
rubocop has the feature to auto-correct small issues, you will need to comment code block one by one and then execute rubocop –auto-correct
Output will be something like: generator/lcd_origin/metadata.rb:5:1: C: [Corrected] 1 trailing blank lines detected.

So in this way we can correct some issues with rubocop. You can repeat the same for rest of the issues.
For the other issues which cannot be correct with rubo-cop you can add rules for the same in .rubocop.yml

We will change the LineLength rule. Add the following in .rubocop.yml file:
Max: 200
Then comment the LineLength block in .rubocop_todo.yml file.

Then execute rubocop You will see there are no offenses.


Edit file recipes/default.rb. Comment everything and add the following:

lazy_message = "Hello World"

file 'lazy_message' do
  path '/tmp/lazy.txt'
  content "#{lazy_message}"

execute 'yum-makecache' do
  command 'yum makecache'
  notifies :create, 'file[message]', :immediately
  action :nothing

package 'bind-utils' do
  action :install
  notifies :run, 'execute[yum-makecache]', :before

file 'message' do
  path '/tmp/message.txt'
  content lazy { "#{lazy_message}" }

lazy_message = "Goodbye World"

In this code firstly value of lazy_meesage will be set to Hello World. Then file /tmp/lazy.txt will get generated then makecache block will ge executed which in turn will call the file “message” block. But the file content will be wriiten at last due to lazy tag. Then package bind-utils will be installed but before that yum makecache will be called.
Then execute kitchen converge
kitchen login
lazy.txt will contain “Hello World” and message.txt will contain “Goodbye world”


Install ruby on centos with rvm


Whether you are preparing your VPS to try out a new application, or find yourself in need of a solid and isolated Ruby installation, getting your system ready-for-work (inline with CentOS design ideologies of stability, along with its incentives of minimalism) can get you feeling a little bit lost.

In this DigitalOcean article, we are focusing on the simplest and quickest rock-solid way to get the latest Ruby interpreter (version 2.1.0) installed on a VPS running CentOS 6.5 using the Ruby Version Manager – RVM.


1. Ruby Version Manager (RVM)

2. Understanding CentOS

3. Getting Started With Installation

  1. Preparing The System
  2. Downloading And Installing RVM
  3. Installing Ruby 2.1.0 On CentOS 6.5 Using RVM
  4. Setting Up Any Ruby Version As The Default Interpreter
  5. Working With Different Ruby Installations
  6. Working With RVM gemsets

Ruby Version Manager (RVM)

Ruby Version Manager, or RVM (and rvm as a command) for short, lets developers and system administrators quickly get started using Ruby and/or developing applications with a Ruby interpreter.

Not only does RVM support multiple versions of Ruby simultaneously, but also it comes with built-in tools to create and work with virtual environments called gemsets. With the help of RVM, it is possible to create any number of perfectly isolated – and self-contained – gemsets where dependencies, packages, and the default Ruby installation are crafted to match your needs and kept accordingly between different stages of deployment — guaranteed to work the same way regardless of where.

RVM gemsets

The power of RVM is its ability to create fully isolated Ruby containers which act like a completely different (and a new) environment. Any application running inside the environment can access (and function) only within its reach.

Understanding CentOS

CentOS operating system is derived from RHEL – Red Hat Enterprise Linux. The target users of these distributions are usually businesses, which require their systems to be running the most stable way for a long time.

The main incentives of CentOS, therefore, is the desire for stability, which is achieved by supplying tested, stable versions of applications.

All the default applications that are shipped with CentOS remain to be used by the system (and its supportive applications such as the package manager YUM) alone. It is neither recommended nor easy to try to work with them.

That is why we are going to prepare our CentOS 6.5 running droplet with necessary tools and continue with installing a Ruby interpreter targeted to run your applications.

Getting Started With Installation

Preparing The System

CentOS distributions are very lean. They do not come with many of the popular applications and tools that you are likely to need – and this is an intentional design choice as we have seen.

For our installations, however, we are going to need some libraries and tools (i.e. development [related] tools) that are not shipped by default. Therefore, we need to get them downloaded and installed before we continue.

For this purpose we will download various development tools using YUM software groups which consist of bunch of commonly used tools (applications) bundled together, ready to download.

As the first step, in order to get necessary development tools, run the following:

yum groupinstall -y development


yum groupinstall -y 'development tools'

Note: The former (shorter) version might not work on older distributions of CentOS.

Downloading And Installing RVM

After arming our system with tools needed for development (and deployment) of applications, such as a generic compiler, we are ready to get RVM downloaded installed.

RVM is designed from the ground up to make the whole process of getting Ruby and managing environments easy. It is no surprise that getting RVM itself is simplified as well.

In order to download and install RVM, run the following:

curl -L | bash -s stable

And to create a system environment using RVM shell script:

source /etc/profile.d/

Installing Ruby 2.1.0 On CentOS 6.5 Using RVM

All that is needed from now on to work with Ruby 2.1.0 (or any other version), after downloading RVM and configuring a system environment is the actual installation of Ruby from source – which is to be handled by RVM.

In order to install Ruby 2.1.0 from source using RVM, run the following:

rvm reload
To find available ruby versions execute rvm list known
It will display ruby versions
rvm install 2.1.0 

Setting Up Any Ruby Version As The Default Interpreter

If you are working with multiple applications which are already in production, it is a highly likely scenario that at some point you will need to use a different version of Ruby for a certain application.

However, for most situations, you will probably be using the latest version as the interpreter to run all others.

One of RVM’s excellent features is its ability to help you set a default Ruby version to be used generally and switch between them when necessary.

To check your current default interpreter, run the following:

ruby --version
# ruby command is linked to the selected version of Ruby Interpreter (i.e. 2.1.0)

To see all the installed Ruby versions, use the following command:

rvm list rubies

To set a Ruby version as the default, run the following:

# Usage: rvm use [version] --default
rvm use 2.1.0 --default

Helm issues

  1. Helm install pod in pending state:
    When you execute kubectl get events you will see the following error:
    no persistent volumes available for this claim and no storage class is set or
    PersistentVolumeClaim is not bound
    This error usually comes in kubernetes set with kubeadm.
    You will need to create persistentvolume with the following yaml file:

    kind: PersistentVolume
    apiVersion: v1
      name: redis-data
        type: local
      storageClassName: generic
        storage: 8Gi
        - ReadWriteOnce
        path: "/bitnami/redis"

    create pv with kubectl create -f pv-create.ymlThen you will need to create pvc with following yaml

    kind: PersistentVolumeClaim
    apiVersion: v1
      name: redis-data
      storageClassName: generic
        - ReadWriteOnce
          storage: 8Gi

    You will need to create pvc with kubectl create -f pv-claim.ymlCheck the pvc status with kubectl get pvc with status should be bound.
    You can pass this Claim with –set persistence.existingClaim=PVC_NAME in helm install command

  2. Hostname not resolvable in helm pods:
    Login to the helm pod container and follow the steps:
    Execute: ping
    route -n You will get an ip for gateway. Check if you can ping the gateway
    In the kubernetes master node check the ip of kube-dns pod with command:
    kubectl get pods -n kube-system -o wide | grep kube-dns this will return an IP in output. In your pod container check if this IP is present as nameserver.
    ifconfig note the IP address range assigned to the container.
    In the kubernetes master node execute ifconfig check that the IP address noted previously belong to which bridge’s IP range.
    If it belongs to some other interface than expected you can check it by executing:
    brctl show check if the bridge has an interface attached to it.
    If not this is the reason the pods do not have an internet connection.
    You can attach the interface with this command:
    brctl addif mybridge eth0
    This issue can be in the weave network, try to do a kubeadm resetand add a flannel network

Installing Kubernetes 1.8.1 on centos 7 with flannel


You should have at least two VMs (1 master and 1 slave) with you before creating cluster in order to test full functionality of k8s.

1] Master :-

Minimum of 1 Gb RAM, 1 CPU core and 50 Gb HDD     ( suggested )

2] Slave :-

Minimum of 1 Gb RAM, 1 CPU core and 50 Gb HDD     ( suggested )

3] Also, make sure of following things.

  • Network interconnectivity between VMs.
  • hostnames
  • Prefer to give Static IP.
  • DNS entries
  • Disable SELinux

$ vi /etc/selinux/config

  • Disable and stop firewall. ( If you are not familiar with firewall )

$ systemctl stop firewalld

$ systemctl disable firewalld

Following steps creates k8s cluster on the above VMs using kubeadm on centos 7.

Step 1] Installing kubelet and kubeadm on all your hosts

$ ARCH=x86_64

$ cat <<EOF > /etc/yum.repos.d/kubernetes.repo









$ setenforce 0

$ yum install -y docker kubelet kubeadm kubectl kubernetes-cni

$ systemctl enable docker && systemctl start docker

$ systemctl enable kubelet && systemctl start kubelet

You might have an issue where the kubelet service does not start. You can see the error in /var/log/messages: If you have an error as follows:
Oct 16 09:55:33 k8s-master kubelet: error: unable to load client CA file /etc/kubernetes/pki/ca.crt: open /etc/kubernetes/pki/ca.crt: no such file or directory
Oct 16 09:55:33 k8s-master systemd: kubelet.service: main process exited, code=exited, status=1/FAILURE

Then you will have to initialize the kubeadm first as in the next step. And the start the kubelet service.

Step 2.1] Initializing your master

$ kubeadm init


  1. execute above command on master node. This command will select one of interface to be used as API server. If you wants to provide another interface please provide “–apiserver-advertise-address=<ip-address>” as an argument. So the whole command will be like this-

$ kubeadm init –apiserver-advertise-address=<ip-address>


  1. K8s has provided flexibility to use network of your choice like flannel, calico etc. I am using flannel network. For flannel network we need to pass network CIDR explicitly. So now the whole command will be-

$ kubeadm init –apiserver-advertise-address=<ip-address> –pod-network-cidr=

Exa:- $  kubeadm init –apiserver-advertise-address= –pod-network-cidr=

Step 2.2] Start using cluster

$ sudo cp /etc/kubernetes/admin.conf $HOME/
$ sudo chown $(id -u):$(id -g) $HOME/admin.conf
$ export KUBECONFIG=$HOME/admin.conf
-> Use same network CIDR as it is also configured in yaml file of flannel that we are going to configure in step 3.

-> At the end you will get one token along with the command, make a note of it, which will be used to join the slaves.


Step 3] Installing a pod network

Different networks are supported by k8s and depends on user choice. For this demo I am using flannel network. As of k8s-1.6, cluster is more secure by default. It uses RBAC ( Role Based Access Control ), so make sure that the network you are going to use has support for RBAC and k8s-1.6.

  1. Create RBAC Pods :

$ kubectl apply -f

Check whether pods are creating or not :

$ kubectl get pods –all-namespaces

  1. Create Flannel pods :

$ kubectl apply -f

Check whether pods are creating or not :

$ kubectl get pods –all-namespaces -o wide

-> at this stage all your pods should be in running state.

-> option “-o wide” will give more details like IP and slave where it is deployed.


Step 4] Joining your nodes


SSH to slave and execute following command to join the existing cluster.

$ kubeadm join –token <token> <master-ip>:<master-port>

You might also have an ca-cert-hash make sure you copy the entire join command from the init output to join the nodes.

Go to master node and see whether new slave has joined or not as-

$ kubectl get nodes

-> If slave is not ready, wait for few seconds, new slave will join soon.


Step 5]  Verify your cluster by running sample nginx application

$ vi  sample_nginx.yaml


apiVersion: apps/v1beta1

kind: Deployment


 name: nginx-deployment


 replicas: 2 # tells deployment to run 2 pods matching the template

 template: # create pods using pod definition in this template


     # unlike pod-nginx.yaml, the name is not included in the meta data as a unique name is

     # generated from the deployment name


       app: nginx



     – name: nginx

       image: nginx:1.7.9


       – containerPort: 80


$ kubectl create -f sample_nginx.yaml


Verify pods are getting created or not.

$ kubectl get pods

$ kubectl get deployments


Now , lets expose the deployment so that the service will be accessible to other pods in the cluster.

$ kubectl expose deployment nginx-deployment –name=nginx-service –port=80 –target-port=80 –type=NodePort


Above command will create service with the name “nginx-service”. Service will be accessible on the port given by “–port” option for the “–target-port”. Target port will be of pod. Service will be accessible within the cluster only. In order to access it using your host IP “NodePort” option will be used.


–type=NodePort :- when this option is given k8s tries to find out  one of free port in the range 30000-32767 on all the VMs of the cluster and binds the underlying service with it. If no such port found then it will return with an error.


Check service is created or not

$ kubectl get svc


Try to curl –

$ curl <service-IP> 80  

From all the VMs including master. Nginx welcome page should be accessible.

$ curl <master-ip> nodePort

$ curl <slave-IP> nodePort

Execute this from all the VMs. Nginx welcome page should be accessible.

Also, Access nginx home page by using browser.

Helm: Installation and Configuration


  • You must have Kubernetes installed. We recommend version 1.4.1 or later.
  • You should also have a local configured copy of kubectl.

Helm will figure out where to install Tiller by reading your Kubernetes configuration file (usually $HOME/.kube/config). This is the same file that kubectl uses.

To find out which cluster Tiller would install to, you can run kubectl config current-contextor kubectl cluster-info.

$ kubectl config current-context


Download a binary release of the Helm client. You can use tools like homebrew, or look at the official releases page.

For more details, or for other options, see the installation guide.


Once you have Helm ready, you can initialize the local CLI and also install Tiller into your Kubernetes cluster in one step:

$ helm init

This will install Tiller into the Kubernetes cluster you saw with kubectl config current-context.

TIP: Want to install into a different cluster? Use the --kube-context flag.

TIP: When you want to upgrade Tiller, just run helm init --upgrade.


To install a chart, you can run the helm install command. Helm has several ways to find and install a chart, but the easiest is to use one of the official stable charts.

$ helm repo update              # Make sure we get the latest list of charts
$ helm install stable/mysql
Released smiling-penguin

In the example above, the stable/mysql chart was released, and the name of our new release is smiling-penguin. You get a simple idea of the features of this MySQL chart by running helm inspect stable/mysql.

Whenever you install a chart, a new release is created. So one chart can be installed multiple times into the same cluster. And each can be independently managed and upgraded.

The helm install command is a very powerful command with many capabilities. To learn more about it, check out the Using Helm Guide


It’s easy to see what has been released using Helm:

$ helm ls
NAME           	VERSION	 UPDATED                       	STATUS         	CHART
smiling-penguin	 1      	Wed Sep 28 12:59:46 2016      	DEPLOYED       	mysql-0.1.0

The helm list function will show you a list of all deployed releases.


To uninstall a release, use the helm delete command:

$ helm delete smiling-penguin
Removed smiling-penguin

This will uninstall smiling-penguin from Kubernetes, but you will still be able to request information about that release:

$ helm status smiling-penguin

Because Helm tracks your releases even after you’ve deleted them, you can audit a cluster’s history, and even undelete a release (with helm rollback).


To learn more about the available Helm commands, use helm help or type a command followed by the -h flag:

$ helm get -h

+++ aliases = [ “”, “docs/”, “using_helm/”, “developing_charts/” ] +++

Installing Helm

There are two parts to Helm: The Helm client (helm) and the Helm server (Tiller). This guide shows how to install the client, and then proceeds to show two ways to install the server.


The Helm client can be installed either from source, or from pre-built binary releases.

From the Binary Releases

Every release of Helm provides binary releases for a variety of OSes. These binary versions can be manually downloaded and installed.

  1. Download your desired version
  2. Unpack it (tar -zxvf helm-v2.0.0-linux-amd64.tgz)
  3. Find the helm binary in the unpacked directory, and move it to its desired destination (mv linux-amd64/helm /usr/local/bin/helm)

From there, you should be able to run the client: helm help.

From Homebrew (macOS)

Members of the Kubernetes community have contributed a Helm formula build to Homebrew. This formula is generally up to date.

brew install kubernetes-helm

(Note: There is also a formula for emacs-helm, which is a different project.)


Helm now has an installer script that will automatically grab the latest version of the Helm client and install it locally.

You can fetch that script, and then execute it locally. It’s well documented so that you can read through it and understand what it is doing before you run it.

$ curl >
$ chmod 700
$ ./

Yes, you can curl | bash that if you want to live on the edge.

From Canary Builds

“Canary” builds are versions of the Helm software that are built from the latest master branch. They are not official releases, and may not be stable. However, they offer the opportunity to test the cutting edge features.

Canary Helm binaries are stored in the Kubernetes Helm GCS bucket. Here are links to the common builds:

From Source (Linux, macOS)

Building Helm from source is slightly more work, but is the best way to go if you want to test the latest (pre-release) Helm version.

You must have a working Go environment with glide and Mercurial installed.

$ cd $GOPATH
$ mkdir -p src/
$ cd src/
$ git clone
$ cd helm
$ make bootstrap build

The bootstrap target will attempt to install dependencies, rebuild the vendor/ tree, and validate configuration.

The build target will compile helm and place it in bin/helm. Tiller is also compiled, and is placed in bin/tiller.


Tiller, the server portion of Helm, typically runs inside of your Kubernetes cluster. But for development, it can also be run locally, and configured to talk to a remote Kubernetes cluster.

Easy In-Cluster Installation

The easiest way to install tiller into the cluster is simply to run helm init. This will validate that helm’s local environment is set up correctly (and set it up if necessary). Then it will connect to whatever cluster kubectl connects to by default (kubectl config view). Once it connects, it will install tiller into the kube-system namespace.

After helm init, you should be able to run kubectl get pods --namespace kube-systemand see Tiller running.

You can explicitly tell helm init to…

  • Install the canary build with the --canary-image flag
  • Install a particular image (version) with --tiller-image
  • Install to a particular cluster with --kube-context
  • Install into a particular namespace with --tiller-namespace

Once Tiller is installed, running helm version should show you both the client and server version. (If it shows only the client version, helm cannot yet connect to the server. Use kubectl to see if any tiller pods are running.)

Helm will look for Tiller in the kube-system namespace unless --tiller-namespace or TILLER_NAMESPACE is set.

Installing Tiller Canary Builds

Canary images are built from the master branch. They may not be stable, but they offer you the chance to test out the latest features.

The easiest way to install a canary image is to use helm init with the --canary-image flag:

$ helm init --canary-image

This will use the most recently built container image. You can always uninstall Tiller by deleting the Tiller deployment from the kube-system namespace using kubectl.

Running Tiller Locally

For development, it is sometimes easier to work on Tiller locally, and configure it to connect to a remote Kubernetes cluster.

The process of building Tiller is explained above.

Once tiller has been built, simply start it:

$ bin/tiller
Tiller running on :44134

When Tiller is running locally, it will attempt to connect to the Kubernetes cluster that is configured by kubectl. (Run kubectl config view to see which cluster that is.)

You must tell helm to connect to this new local Tiller host instead of connecting to the one in-cluster. There are two ways to do this. The first is to specify the --host option on the command line. The second is to set the $HELM_HOST environment variable.

$ export HELM_HOST=localhost:44134
$ helm version # Should connect to localhost.
Client: &version.Version{SemVer:"v2.0.0-alpha.4", GitCommit:"db...", GitTreeState:"dirty"}
Server: &version.Version{SemVer:"v2.0.0-alpha.4", GitCommit:"a5...", GitTreeState:"dirty"}

Importantly, even when running locally, Tiller will store release configuration in ConfigMaps inside of Kubernetes.


As of Helm 2.2.0, Tiller can be upgraded using helm init --upgrade.

For older versions of Helm, or for manual upgrades, you can use kubectl to modify the Tiller image:

$ export TILLER_TAG=v2.0.0-beta.1        # Or whatever version you want
$ kubectl --namespace=kube-system set image deployments/tiller-deploy$TILLER_TAG
deployment "tiller-deploy" image updated

Setting TILLER_TAG=canary will get the latest snapshot of master.


Because Tiller stores its data in Kubernetes ConfigMaps, you can safely delete and re-install Tiller without worrying about losing any data. The recommended way of deleting Tiller is with kubectl delete deployment tiller-deploy --namespace kube-system, or more concisely helm reset.

Tiller can then be re-installed from the client with:

$ helm init


In most cases, installation is as simple as getting a pre-built helm binary and running helm init. This document covers additional cases for those who want to do more sophisticated things with Helm.

Once you have the Helm Client and Tiller successfully installed, you can move on to using Helm to manage charts.

+++ aliases = [ “”, “docs/”, “using_helm/”, “developing_charts/” ] +++

Kubernetes Distribution Guide

This document captures information about using Helm in specific Kubernetes environments.

We are trying to add more details to this document. Please contribute via Pull Requests if you can.


Helm is tested and known to work with minikube. It requires no additional configuration.


Hyperkube configured via scripts/ is known to work. For raw Hyperkube you may need to do some manual configuration.


Google’s GKE hosted Kubernetes platform is known to work with Helm, and requires no additional configuration.


Kubernetes bootstrapped with kubeadm is known to work on the following Linux distributions:

  • Ubuntu 16.04

Some versions of Helm (v2.0.0-beta2) require you to export KUBECONFIG=/etc/kubernetes/admin.conf or create a ~/.kube/config.


Helm requires that kubelet have access to a copy of the socat program to proxy connections to the Tiller API. On Container Linux the Kubelet runs inside of a hyperkube container image that has socat. So, even though Container Linux doesn’t ship socat the container filesystem running kubelet does have socat. To learn more read the Kubelet Wrapper docs.

+++ aliases = [ “”, “docs/”, “using_helm/”, “developing_charts/” ] +++

Installation: Frequently Asked Questions

This section tracks some of the more frequently encountered issues with installing or getting started with Helm.

We’d love your help making this document better. To add, correct, or remove information, file an issue or send us a pull request.


I want to know more about my downloading options.

Q: I can’t get to GitHub releases of the newest Helm. Where are they?

A: We no longer use GitHub releases. Binaries are now stored in a GCS public bucket.

Q: Why aren’t there Debian/Fedora/… native packages of Helm?

We’d love to provide these or point you toward a trusted provider. If you’re interested in helping, we’d love it. This is how the Homebrew formula was started.

Q: Why do you provide a curl ...|bash script?

A: There is a script in our repository (scripts/get) that can be executed as a curl ..|bashscript. The transfers are all protected by HTTPS, and the script does some auditing of the packages it fetches. However, the script has all the usual dangers of any shell script.

We provide it because it is useful, but we suggest that users carefully read the script first. What we’d really like, though, are better packaged releases of Helm.


I’m trying to install Helm/Tiller, but something is not right.

Q: How do I put the Helm client files somewhere other than ~/.helm?

Set the $HELM_HOME environment variable, and then run helm init:

export HELM_HOME=/some/path
helm init --client-only

Note that if you have existing repositories, you will need to re-add them with helm repo add....

Q: How do I configure Helm, but not install Tiller?

A: By default, helm init will ensure that the local $HELM_HOME is configured, and then install Tiller on your cluster. To locally configure, but not install Tiller, use helm init --client-only.

Q: How do I manually install Tiller on the cluster?

A: Tiller is installed as a Kubernetes deployment. You can get the manifest by running helm init --dry-run --debug, and then manually install it with kubectl. It is suggested that you do not remove or change the labels on that deployment, as they are sometimes used by supporting scripts and tools.

Q: Why do I get Error response from daemon: target is unknown during Tiller install?

A: Users have reported being unable to install Tiller on Kubernetes instances that are using Docker 1.13.0. The root cause of this was a bug in Docker that made that one version incompatible with images pushed to the Docker registry by earlier versions of Docker.

This issue was fixed shortly after the release, and is available in Docker 1.13.1-RC1 and later.


I successfully installed Helm/Tiller but I can’t use it.

Q: Trying to use Helm, I get the error “client transport was broken”

E1014 02:26:32.885226   16143 portforward.go:329] an error occurred forwarding 37008 -> 44134: error forwarding port 44134 to pod tiller-deploy-2117266891-e4lev_kube-system, uid : unable to do port forwarding: socat not found.
2016/10/14 02:26:32 transport: http2Client.notifyError got notified that the client transport was broken EOF.
Error: transport is closing

A: This is usually a good indication that Kubernetes is not set up to allow port forwarding.

Typically, the missing piece is socat. If you are running CoreOS, we have been told that it may have been misconfigured on installation. The CoreOS team recommends reading this:

Here are a few resolved issues that may help you get started:

Q: Trying to use Helm, I get the error “lookup XXXXX on no such host”

Error: Error forwarding ports: error upgrading connection: dial tcp: lookup kube-4gb-lon1-02 on no such host

A: We have seen this issue with Ubuntu and Kubeadm in multi-node clusters. The issue is that the nodes expect certain DNS records to be obtainable via global DNS. Until this is resolved upstream, you can work around the issue as follows:

1) Add entries to /etc/hosts on the master mapping your hostnames to their public IPs 2) Install dnsmasq on the master (e.g. apt install -y dnsmasq) 3) Kill the k8s api server container on master (kubelet will recreate it) 4) Then systemctl restart docker (or reboot the master) for it to pick up the /etc/resolv.conf changes

See this issue for more information:

Q: On GKE (Google Container Engine) I get “No SSH tunnels currently open”

Error: Error forwarding ports: error upgrading connection: No SSH tunnels currently open. Were the targets able to accept an ssh-key for user "gke-[redacted]"?

Another variation of the error message is:

Unable to connect to the server: x509: certificate signed by unknown authority

A: The issue is that your local Kubernetes config file must have the correct credentials.

When you create a cluster on GKE, it will give you credentials, including SSL certificates and certificate authorities. These need to be stored in a Kubernetes config file (Default: ~/.kube/config so that kubectl and helm can access them.

Q: When I run a Helm command, I get an error about the tunnel or proxy

A: Helm uses the Kubernetes proxy service to connect to the Tiller server. If the command kubectl proxy does not work for you, neither will Helm. Typically, the error is related to a missing socat service.

Q: Tiller crashes with a panic

When I run a command on Helm, Tiller crashes with an error like this:

Tiller is listening on :44134
Probes server is listening on :44135
Storage driver is ConfigMap
Cannot initialize Kubernetes connection: the server has asked for the client to provide credentials 2016-12-20 15:18:40.545739 I | storage.go:37: Getting release "bailing-chinchilla" (v1) from storage
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x8053d5]

goroutine 77 [running]:
panic(0x1abbfc0, 0xc42000a040)
        /usr/local/go/src/runtime/panic.go:500 +0x1a1*ConfigMaps).Get(0xc4200c6200, 0xc420536100, 0x15, 0x1ca7431, 0x6, 0xc42016b6a0)
        /home/ubuntu/.go_workspace/src/ +0x75*ConfigMaps).Get(0xc4201d6190, 0xc420536100, 0x15, 0xc420536100, 0x15, 0xc4205360c0)
        /home/ubuntu/.go_workspace/src/ +0x62*Storage).Get(0xc4201d61a0, 0xc4205360c0, 0x12, 0xc400000001, 0x12, 0x0, 0xc420200070)
        /home/ubuntu/.go_workspace/src/ +0x160*ReleaseServer).uniqName(0xc42002a000, 0x0, 0x0, 0xc42016b800, 0xd66a13, 0xc42055a040, 0xc420558050, 0xc420122001)
        /home/ubuntu/.go_workspace/src/ +0xd7*ReleaseServer).prepareRelease(0xc42002a000, 0xc42027c1e0, 0xc42002a001, 0xc42016bad0, 0xc42016ba08)
        /home/ubuntu/.go_workspace/src/ +0x71*ReleaseServer).InstallRelease(0xc42002a000, 0x7f284c434068, 0xc420250c00, 0xc42027c1e0, 0x0, 0x31a9, 0x31a9)
        /home/ubuntu/.go_workspace/src/ +0x78, 0xc42002a000, 0x7f284c434068, 0xc420250c00, 0xc42027c190, 0x0, 0x0, 0x0, 0x0, 0x0)
        /home/ubuntu/.go_workspace/src/ +0x27d*Server).processUnaryRPC(0xc4202f3ea0, 0x28610a0, 0xc420078000, 0xc420264690, 0xc420166150, 0x288cbe8, 0xc420250bd0, 0x0, 0x0)
        /home/ubuntu/.go_workspace/src/ +0xc50*Server).handleStream(0xc4202f3ea0, 0x28610a0, 0xc420078000, 0xc420264690, 0xc420250bd0)
        /home/ubuntu/.go_workspace/src/ +0x6b0*Server).serveStreams.func1.1(0xc420124710, 0xc4202f3ea0, 0x28610a0, 0xc420078000, 0xc420264690)
        /home/ubuntu/.go_workspace/src/ +0xab
created by*Server).serveStreams.func1
        /home/ubuntu/.go_workspace/src/ +0xa3

A: Check your security settings for Kubernetes.

A panic in Tiller is almost always the result of a failure to negotiate with the Kubernetes API server (at which point Tiller can no longer do anything useful, so it panics and exits).

Often, this is a result of authentication failing because the Pod in which Tiller is running does not have the right token.

To fix this, you will need to change your Kubernetes configuration. Make sure that --service-account-private-key-file from controller-manager and --service-account-key-filefrom apiserver point to the same x509 RSA key.


My Helm used to work, then I upgrade. Now it is broken.

Q: After upgrade, I get the error “Client version is incompatible”. What’s wrong?

Tiller and Helm have to negotiate a common version to make sure that they can safely communicate without breaking API assumptions. That error means that the version difference is too great to safely continue. Typically, you need to upgrade Tiller manually for this.

The Installation Guide has definitive information about safely upgrading Helm and Tiller.

The rules for version numbers are as follows:

  • Pre-release versions are incompatible with everything else. Alpha.1 is incompatible with Alpha.2.
  • Patch revisions are compatible: 1.2.3 is compatible with 1.2.4
  • Minor revisions are not compatible: 1.2.0 is not compatible with 1.3.0, though we may relax this constraint in the future.
  • Major revisions are not compatible: 1.0.0 is not compatible with 2.0.0.


I am trying to remove stuff.

Q: When I delete the Tiller deployment, how come all the releases are still there?

Releases are stored in ConfigMaps inside of the kube-system namespace. You will have to manually delete them to get rid of the record.

Q: I want to delete my local Helm. Where are all its files?

Along with the helm binary, Helm stores some files in $HELM_HOME, which is located by default in ~/.helm.

+++ aliases = [ “”, “docs/”, “using_helm/”, “developing_charts/” ] +++

Using Helm

This guide explains the basics of using Helm (and Tiller) to manage packages on your Kubernetes cluster. It assumes that you have already installed the Helm client and the Tiller server (typically by helm init).

If you are simply interested in running a few quick commands, you may wish to begin with the Quickstart Guide. This chapter covers the particulars of Helm commands, and explains how to use Helm.


Chart is a Helm package. It contains all of the resource definitions necessary to run an application, tool, or service inside of a Kubernetes cluster. Think of it like the Kubernetes equivalent of a Homebrew formula, an Apt dpkg, or a Yum RPM file.

Repository is the place where charts can be collected and shared. It’s like Perl’s CPAN archive or the Fedora Package Database, but for Kubernetes packages.

Release is an instance of a chart running in a Kubernetes cluster. One chart can often be installed many times into the same cluster. And each time it is installed, a new release is created. Consider a MySQL chart. If you want two databases running in your cluster, you can install that chart twice. Each one will have its own release, which will in turn have its own release name.

With these concepts in mind, we can now explain Helm like this:

Helm installs charts into Kubernetes, creating a new release for each installation. And to find new charts, you can search Helm chart repositories.


When you first install Helm, it is preconfigured to talk to the official Kubernetes charts repository. This repository contains a number of carefully curated and maintained charts. This chart repository is named stable by default.

You can see which charts are available by running helm search:

$ helm search
stable/drupal   	0.3.2   	One of the most versatile open source content m...
stable/jenkins  	0.1.0   	A Jenkins Helm chart for Kubernetes.
stable/mariadb  	0.5.1   	Chart for MariaDB
stable/mysql    	0.1.0   	Chart for MySQL

With no filter, helm search shows you all of the available charts. You can narrow down your results by searching with a filter:

$ helm search mysql
stable/mysql  	0.1.0  	Chart for MySQL
stable/mariadb	0.5.1  	Chart for MariaDB

Now you will only see the results that match your filter.

Why is mariadb in the list? Because its package description relates it to MySQL. We can use helm inspect chart to see this:

$ helm inspect stable/mariadb
Fetched stable/mariadb to mariadb-0.5.1.tgz
description: Chart for MariaDB
engine: gotpl
- mariadb
- mysql
- database
- sql

Search is a good way to find available packages. Once you have found a package you want to install, you can use helm install to install it.


To install a new package, use the helm install command. At its simplest, it takes only one argument: The name of the chart.

$ helm install stable/mariadb
Fetched stable/mariadb-0.3.0 to /Users/mattbutcher/Code/Go/src/
Last Deployed: Wed Sep 28 12:32:28 2016
Namespace: default

==> extensions/Deployment
happy-panda-mariadb   1         0         0            0           1s

==> v1/Secret
NAME                     TYPE      DATA      AGE
happy-panda-mariadb   Opaque    2         1s

==> v1/Service
NAME                     CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
happy-panda-mariadb    <none>        3306/TCP   1s

MariaDB can be accessed via port 3306 on the following DNS name from within your cluster:

To connect to your database run the following command:

   kubectl run happy-panda-mariadb-client --rm --tty -i --image bitnami/mariadb --command -- mysql -h happy-panda-mariadb

Now the mariadb chart is installed. Note that installing a chart creates a new release object. The release above is named happy-panda. (If you want to use your own release name, simply use the --name flag on helm install.)

During installation, the helm client will print useful information about which resources were created, what the state of the release is, and also whether there are additional configuration steps you can or should take.

Helm does not wait until all of the resources are running before it exits. Many charts require Docker images that are over 600M in size, and may take a long time to install into the cluster.

To keep track of a release’s state, or to re-read configuration information, you can use helm status:

$ helm status happy-panda
Last Deployed: Wed Sep 28 12:32:28 2016
Namespace: default

==> v1/Service
NAME                     CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
happy-panda-mariadb    <none>        3306/TCP   4m

==> extensions/Deployment
happy-panda-mariadb   1         1         1            1           4m

==> v1/Secret
NAME                     TYPE      DATA      AGE
happy-panda-mariadb   Opaque    2         4m

MariaDB can be accessed via port 3306 on the following DNS name from within your cluster:

To connect to your database run the following command:

   kubectl run happy-panda-mariadb-client --rm --tty -i --image bitnami/mariadb --command -- mysql -h happy-panda-mariadb

The above shows the current state of your release.

Customizing the Chart Before Installing

Installing the way we have here will only use the default configuration options for this chart. Many times, you will want to customize the chart to use your preferred configuration.

To see what options are configurable on a chart, use helm inspect values:

helm inspect values stable/mariadb
Fetched stable/mariadb-0.3.0.tgz to /Users/mattbutcher/Code/Go/src/
## Bitnami MariaDB image version
## ref:
## Default: none
imageTag: 10.1.14-r3

## Specify a imagePullPolicy
## Default to 'Always' if imageTag is 'latest', else set to 'IfNotPresent'
## ref:
# imagePullPolicy:

## Specify password for root user
## ref:
# mariadbRootPassword:

## Create a database user
## ref:
# mariadbUser:
# mariadbPassword:

## Create a database
## ref:
# mariadbDatabase:

You can then override any of these settings in a YAML formatted file, and then pass that file during installation.

$ echo '{mariadbUser: user0, mariadbDatabase: user0db}' > config.yaml
$ helm install -f config.yaml stable/mariadb

The above will create a default MariaDB user with the name user0, and grant this user access to a newly created user0db database, but will accept all the rest of the defaults for that chart.

There are two ways to pass configuration data during install:

  • --values (or -f): Specify a YAML file with overrides. This can be specified multiple times and the rightmost file will take precedence
  • --set: Specify overrides on the command line.

If both are used, --set values are merged into --values with higher precedence.

The Format and Limitations of --set

The --set option takes zero or more name/value pairs. At its simplest, it is used like this: --set name=value. The YAML equivalent of that is:

name: value

Multiple values are separated by , characters. So --set a=b,c=d becomes:

a: b
c: d

More complex expressions are supported. For example, --set outer.inner=value is translated into this:

  inner: value

Lists can be expressed by enclosing values in { and }. For example, --set name={a, b, c} translates to:

  - a
  - b
  - c

As of Helm 2.5.0, it is possible to access list items using an array index syntax. For example, --set servers[0].port=80 becomes:

  - port: 80

Multiple values can be set this way. The line --set servers[0].port=80,servers[0].host=example becomes:

  - port: 80
    host: example

Sometimes you need to use special characters in your --set lines. You can use a backslash to escape the characters; --set name=value1\,value2 will become:

name: "value1,value2"

Similarly, you can escape dot sequences as well, which may come in handy when charts use the toYaml function to parse annotations, labels and node selectors. The syntax for --set nodeSelector."kubernetes\.io/role"=master becomes:

nodeSelector: master

Deeply nested data structures can be difficult to express using --set. Chart designers are encouraged to consider the --set usage when designing the format of a values.yaml file.

More Installation Methods

The helm install command can install from several sources:

  • A chart repository (as we’ve seen above)
  • A local chart archive (helm install foo-0.1.1.tgz)
  • An unpacked chart directory (helm install path/to/foo)
  • A full URL (helm install


When a new version of a chart is released, or when you want to change the configuration of your release, you can use the helm upgrade command.

An upgrade takes an existing release and upgrades it according to the information you provide. Because Kubernetes charts can be large and complex, Helm tries to perform the least invasive upgrade. It will only update things that have changed since the last release.

$ helm upgrade -f panda.yaml happy-panda stable/mariadb
Fetched stable/mariadb-0.3.0.tgz to /Users/mattbutcher/Code/Go/src/
happy-panda has been upgraded. Happy Helming!
Last Deployed: Wed Sep 28 12:47:54 2016
Namespace: default

In the above case, the happy-panda release is upgraded with the same chart, but with a new YAML file:

mariadbUser: user1

We can use helm get values to see whether that new setting took effect.

$ helm get values happy-panda
mariadbUser: user1

The helm get command is a useful tool for looking at a release in the cluster. And as we can see above, it shows that our new values from panda.yaml were deployed to the cluster.

Now, if something does not go as planned during a release, it is easy to roll back to a previous release using helm rollback [RELEASE] [REVISION].

$ helm rollback happy-panda 1

The above rolls back our happy-panda to its very first release version. A release version is an incremental revision. Every time an install, upgrade, or rollback happens, the revision number is incremented by 1. The first revision number is always 1. And we can use helm history [RELEASE] to see revision numbers for a certain release.


There are several other helpful options you can specify for customizing the behavior of Helm during an install/upgrade/rollback. Please note that this is not a full list of cli flags. To see a description of all flags, just run helm <command> --help.

  • --timeout: A value in seconds to wait for Kubernetes commands to complete This defaults to 300 (5 minutes)
  • --wait: Waits until all Pods are in a ready state, PVCs are bound, Deployments have minimum (Desired minus maxUnavailable) Pods in ready state and Services have an IP address (and Ingress if a LoadBalancer) before marking the release as successful. It will wait for as long as the--timeout value. If timeout is reached, the release will be marked as FAILED.

Note: In scenario where Deployment has replicas set to 1 and maxUnavailable is not set to 0 as part of rolling update strategy, --wait will return as ready as it has satisfied the minimum Pod in ready condition. – --no-hooks: This skips running hooks for the command – --recreate-pods (only available for upgrade and rollback): This flag will cause all pods to be recreated (with the exception of pods belonging to deployments)


When it is time to uninstall or delete a release from the cluster, use the helm deletecommand:

$ helm delete happy-panda

This will remove the release from the cluster. You can see all of your currently deployed releases with the helm list command:

$ helm list
NAME           	VERSION	UPDATED                        	STATUS         	CHART
inky-cat       	1      	Wed Sep 28 12:59:46 2016       	DEPLOYED       	alpine-0.1.0

From the output above, we can see that the happy-panda release was deleted.

However, Helm always keeps records of what releases happened. Need to see the deleted releases? helm list --deleted shows those, and helm list --all shows all of the releases (deleted and currently deployed, as well as releases that failed):

⇒  helm list --all
NAME           	VERSION	UPDATED                        	STATUS         	CHART
happy-panda   	2      	Wed Sep 28 12:47:54 2016       	DELETED        	mariadb-0.3.0
inky-cat       	1      	Wed Sep 28 12:59:46 2016       	DEPLOYED       	alpine-0.1.0
kindred-angelf 	2      	Tue Sep 27 16:16:10 2016       	DELETED        	alpine-0.1.0

Because Helm keeps records of deleted releases, a release name cannot be re-used. (If you really need to re-use a release name, you can use the --replace flag, but it will simply re-use the existing release and replace its resources.)

Note that because releases are preserved in this way, you can rollback a deleted resource, and have it re-activate.


So far, we’ve been installing charts only from the stable repository. But you can configure helm to use other repositories. Helm provides several repository tools under the helm repocommand.

You can see which repositories are configured using helm repo list:

$ helm repo list
NAME           	URL
local          	http://localhost:8879/charts

And new repositories can be added with helm repo add:

$ helm repo add dev

Because chart repositories change frequently, at any point you can make sure your Helm client is up to date by running helm repo update.


The Chart Development Guide explains how to develop your own charts. But you can get started quickly by using the helm create command:

$ helm create deis-workflow
Creating deis-workflow

Now there is a chart in ./deis-workflow. You can edit it and create your own templates.

As you edit your chart, you can validate that it is well-formatted by running helm lint.

When it’s time to package the chart up for distribution, you can run the helm packagecommand:

$ helm package deis-workflow

And that chart can now easily be installed by helm install:

$ helm install ./deis-workflow-0.1.0.tgz

Charts that are archived can be loaded into chart repositories. See the documentation for your chart repository server to learn how to upload.

Note: The stable repository is managed on the Kubernetes Charts GitHub repository. That project accepts chart source code, and (after audit) packages those for you.


In some cases you may wish to scope Tiller or deploy multiple Tillers to a single cluster. Here are some best practices when operating in those circumstances.

  1. Tiller can be installed into any namespace. By default, it is installed into kube-system. You can run multiple Tillers provided they each run in their own namespace.
  2. Limiting Tiller to only be able to install into specific namespaces and/or resource types is controlled by Kubernetes RBAC roles and rolebindings. You can add a service account to Tiller when configuring Helm via helm init --service-acount <NAME>. You can find more information about that here.
  3. Release names are unique PER TILLER INSTANCE.
  4. Charts should only contain resources that exist in a single namespace.
  5. It is not recommended to have multiple Tillers configured to manage resources in the same namespace.


This chapter has covered the basic usage patterns of the helm client, including searching, installation, upgrading, and deleting. It has also covered useful utility commands like helm statushelm get, and helm repo.

For more information on these commands, take a look at Helm’s built-in help: helm help.

In the next chapter, we look at the process of developing charts.

+++ aliases = [ “”, “docs/”, “using_helm/”, “developing_charts/” ] +++

The Helm Plugins Guide

Helm 2.1.0 introduced the concept of a client-side Helm plugin. A plugin is a tool that can be accessed through the helm CLI, but which is not part of the built-in Helm codebase.

Existing plugins can be found on related section or by searching Github.

This guide explains how to use and create plugins.


Helm plugins are add-on tools that integrate seamlessly with Helm. They provide a way to extend the core feature set of Helm, but without requiring every new feature to be written in Go and added to the core tool.

Helm plugins have the following features:

  • They can be added and removed from a Helm installation without impacting the core Helm tool.
  • They can be written in any programming language.
  • They integrate with Helm, and will show up in helm help and other places.

Helm plugins live in $(helm home)/plugins.

The Helm plugin model is partially modeled on Git’s plugin model. To that end, you may sometimes hear helm referred to as the porcelain layer, with plugins being the plumbing. This is a shorthand way of suggesting that Helm provides the user experience and top level processing logic, while the plugins do the “detail work” of performing a desired action.


A Helm plugin management system is in the works. But in the short term, plugins are installed by copying the plugin directory into $(helm home)/plugins.

$ cp -a myplugin/ $(helm home)/plugins/

If you have a plugin tar distribution, simply untar the plugin into the $(helm home)/pluginsdirectory.


In many ways, a plugin is similar to a chart. Each plugin has a top-level directory, and then a plugin.yaml file.

$(helm home)/plugins/
  |- keybase/
      |- plugin.yaml

In the example above, the keybase plugin is contained inside of a directory named keybase. It has two files: plugin.yaml (required) and an executable script, (optional).

The core of a plugin is a simple YAML file named plugin.yaml. Here is a plugin YAML for a plugin that adds support for Keybase operations:

name: "keybase"
version: "0.1.0"
usage: "Integrate tools with Helm"
description: |-
  This plugin provides Keybase services to Helm.
ignoreFlags: false
useTunnel: false
command: "$HELM_PLUGIN_DIR/"

The name is the name of the plugin. When Helm executes it plugin, this is the name it will use (e.g. helm NAME will invoke this plugin).

name should match the directory name. In our example above, that means the plugin with name: keybase should be contained in a directory named keybase.

Restrictions on name:

  • name cannot duplicate one of the existing helm top-level commands.
  • name must be restricted to the characters ASCII a-z, A-Z, 0-9, _ and -.

version is the SemVer 2 version of the plugin. usage and description are both used to generate the help text of a command.

The ignoreFlags switch tells Helm to not pass flags to the plugin. So if a plugin is called with helm myplugin --foo and ignoreFlags: true, then --foo is silently discarded.

The useTunnel switch indicates that the plugin needs a tunnel to Tiller. This should be set to true anytime a plugin talks to Tiller. It will cause Helm to open a tunnel, and then set $TILLER_HOST to the right local address for that tunnel. But don’t worry: if Helm detects that a tunnel is not necessary because Tiller is running locally, it will not create the tunnel.

Finally, and most importantly, command is the command that this plugin will execute when it is called. Environment variables are interpolated before the plugin is executed. The pattern above illustrates the preferred way to indicate where the plugin program lives.

There are some strategies for working with plugin commands:

  • If a plugin includes an executable, the executable for a command: should be packaged in the plugin directory.
  • The command: line will have any environment variables expanded before execution. $HELM_PLUGIN_DIR will point to the plugin directory.
  • The command itself is not executed in a shell. So you can’t oneline a shell script.
  • Helm injects lots of configuration into environment variables. Take a look at the environment to see what information is available.
  • Helm makes no assumptions about the language of the plugin. You can write it in whatever you prefer.
  • Commands are responsible for implementing specific help text for -h and --help. Helm will use usage and description for helm help and helm help myplugin, but will not handle helm myplugin --help.


When Helm executes a plugin, it passes the outer environment to the plugin, and also injects some additional environment variables.

Variables like KUBECONFIG are set for the plugin if they are set in the outer environment.

The following variables are guaranteed to be set:

  • HELM_PLUGIN: The path to the plugins directory
  • HELM_PLUGIN_NAME: The name of the plugin, as invoked by helm. So helm myplug will have the short name myplug.
  • HELM_PLUGIN_DIR: The directory that contains the plugin.
  • HELM_BIN: The path to the helm command (as executed by the user).
  • HELM_HOME: The path to the Helm home.
  • HELM_PATH_*: Paths to important Helm files and directories are stored in environment variables prefixed by HELM_PATH.
  • TILLER_HOST: The domain:port to Tiller. If a tunnel is created, this will point to the local endpoint for the tunnel. Otherwise, it will point to $HELM_HOST--host, or the default host (according to Helm’s rules of precedence).

While HELM_HOST may be set, there is no guarantee that it will point to the correct Tiller instance. This is done to allow plugin developer to access HELM_HOST in its raw state when the plugin itself needs to manually configure a connection.


If a plugin specifies useTunnel: true, Helm will do the following (in order):

  1. Parse global flags and the environment
  2. Create the tunnel
  4. Execute the plugin
  5. Close the tunnel

The tunnel is removed as soon as the command returns. So, for example, a command cannot background a process and assume that that process will be able to use the tunnel.


When executing a plugin, Helm will parse global flags for its own use. Some of these flags are not passed on to the plugin.

  • --debug: If this is specified, $HELM_DEBUG is set to 1
  • --home: This is converted to $HELM_HOME
  • --host: This is converted to $HELM_HOST
  • --kube-context: This is simply dropped. If your plugin uses useTunnel, this is used to set up the tunnel for you.

Plugins should display help text and then exit for -h and --help. In all other cases, plugins may use flags as appropriate.

Serverless Architectures: Monoliths, Nanoservices, Microservices & Hybrids

The Monolith

Whenever I hear “monolith”, I think of a massive LAMP project with a single, burning hot MySQL database.

monolith server
(not always the case). The monolith architecture looks something like this in Serverless:

I.e. all requests to go to a single Lambda function, app.js. Users and games have nothing to do with one another but the application logic for users and games are in the same Lambda function.


We found that the greatest advantage that the monolith had over nanoservices and microservices was speed of deployment. With nanoservices and microservices, you have to deploy multiple copies of dependant node_modules (with Node.js) and any library code that your functions share which can be slow. With the monolith, it’s a single function deployment to all API endpoints so deployment is faster. On the other hand, how common is it to want to deploy all endpoints…


This architecture in Serverless, has similar drawbacks to the monolithic architecture in general:

  • Tighter coupling. In the example above, app.js is responsible for both users and games. If a bug gets introduced into the users part of the function, it’s more likely that the games part might break too
  • Complexity. If all application logic is in a single function, the function can get more complicated which can slow down development and make it easier to introduce bugs


Microservices in Serverless looks something like this:


The advantages of the microservices architecture in Serverless inherits advantages of microservices in general. A couple:

  • Separation of concerns. If a bug has been introduced into games.js, calls to users.js should carry on working. Of course, maybe GET /users/:id might contact the games microservice to get games that the user plays but if users.js and games.js are proper microservices then users.js should handle games.js failing and vice versa
  • Less complexity. Adding additional functionality to games.js doesn’t make the users.js codebase any more complicated


As mentioned above with the monolith, microservices in Serverless can result in slower deployments.


Nanoservices take microservices to the extreme – one function per endpoint instead of one per resource:


Nanoservices take the advantages of microservices and amplifies them. Separation of concerns is greater – get_users.js is probably going to be simpler than users.js that handles everything to do with a user.


Again, similar to microservices but even more so – the number of functions to deploy can get huge so deployment time can increase.


There is nothing to stop Developers taking a hybrid approach to their architecture e.g. a mixture of nanoservices and microservices. In our example, if there was a lot of game functionality, it might make sense to split the functionality into nanoservices but if is less functionality related to users, microservices could be more appropriate:

If you have any examples of how you’re architecting your Serverless projects, tell us about it!