How to develop a Stratos docker cartridge for CouchDB

This cartridges is developed for use in Apache Stratos. Which is design and developed by WSO2 Inc. This is a PAAS environment lately donated to Apache foundation by WSO2. In order to develop a cartridge I need an up and running Stratos instance on top of Openstack. Openstack is an IAAS provider that I’m using, you can use Amazon EC2 as well. 

Note : Refer this valuable article if you wanted to integrate Stratos with Amazon EC2.  

CouchDB is a database that completely embraces the web. Store your data with JSON documents. Access your documents and query your indexes with your web browser, via HTTP. Index, combine, and transform your documents with JavaScript. CouchDB works well with modern web and mobile apps. You can even serve web apps directly out of CouchDB. And you can distribute your data, or your apps, efficiently using CouchDB’s incremental replication. CouchDB supports master-master setups with automatic conflict detection.

Docker is an open platform for developers and sysadmins to build, ship, and run distributed applications. Consisting of Docker Engine, a portable, lightweight runtime and packaging tool, and Docker Hub, a cloud service for sharing applications and automating workflows, Docker enables apps to be quickly assembled from components and eliminates the friction between development, QA, and production environments. As a result, IT can ship faster and run the same app, unchanged, on laptops, data center VMs, and any cloud.

Since this article is to develop a docker cartridge author believes that reader already configured Openstack with docker and Apache Startos on top of that. If not please follow these blog articles which is the best article that explain way of doing it.


After configuration the architecture of Openstack with Docker should be like this :
First of all clone my github project of developed CouchDB docker cartridge. 
$ git clone https://github.com/thushara35/startos-docker-couchdb-cartridge.git
This will create the below folder structure.
Note: Please ignore the .DS_Store files since it’s an auto generated system file which we don't need to take care of.

Now just open the cloned folder and open the Dockerfile in a text editor you prefer. 
Now I will review this file line by line. Sometimes I might explain several lines together because it is so difficult to explain when it is pretty straight forward to understand.  
FROM ubuntu:12.04
This command will download the ubuntu 12.04 cloud image which is Im going to use for the creation of CouchDB cartridge.
ENV DEBIAN_FRONTEND noninteractive
Above command will hide debian related error messages which generated in the docker build. It's better to comment this line if using this for educational purposes. Ignore and comment above line if what Im talking is like rocket signs.

Now we are done with the environment setting up, it's time to install all necessary dependencies.

RUN apt-get -y update --fix-missing
UNIX update will download all the needed commands. --fix-missing is to recheck whether there is any changes in the update.

RUN apt-get install -y openssh-server
RUN echo 'root:password' |chpasswd
RUN mkdir -p /var/run/sshd


These three lines are used to grant secure shell access to docker instance when it is active.

stratos@Dev-PC:~$ ssh root@10.11.12.2
The authenticity of host '10.11.12.2 (10.11.12.2)' can't be established.
ECDSA key fingerprint is 74:95:b3:5f:22:5a:dc:70:8c:ab:50:c8:45:65:77:9d.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '10.11.12.2' (ECDSA) to the list of known hosts.
root@10.11.12.2's password: <password gave in line 24>Welcome to Ubuntu 12.04.5 LTS (GNU/Linux 3.11.0-26-generic x86_64)
the exact distribution terms for each program are described in the
the exact distribution terms for each program are described in the
 * Documentation:  https://help.ubuntu.com/
The programs included with the Ubuntu system are free software;
individual files in /usr/share/doc/*/copyright.
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law. 
root@instance-00000001:~#

Using above command in bold you can log into the docker instance 

Note: 10.11.12.2 is the private URL generated by the openstack for the instance it might be different for you.

Let's add CouchDB stuff to the docker image.

To implement this I refer this github project which gives me lot of insight of how to develop this cartridge.

RUN echo "deb http://us.archive.ubuntu.com/ubuntu/ precise universe" >> /etc/apt/sources.list


RUN apt-get install -y apt-utils g++
RUN apt-get install -y erlang-dev erlang-manpages erlang-base-hipe erlang-eunit erlang-nox erlang-xmerl erlang-inets

RUN apt-get install -y libmozjs185-dev libicu-dev libcurl4-gnutls-dev libtool wget

    #
    # Please check the mirror is working or not.
    #
RUN apt-get install -y make

RUN cd /tmp ; wget http://mirror.nexcess.net/apache/couchdb/source/1.6.1/apache-couchdb-1.6.1.tar.gz

RUN cd /tmp && tar xvzf apache-couchdb-1.6.1.tar.gz
RUN cd /tmp/apache-couchdb-* ; ./configure && make install

RUN printf "[httpd]\nport = 5984\nbind_address = 0.0.0.0" > /usr/local/etc/couchdb/local.d/docker.ini

Now you’re done with installing CouchDB, later you need to expose the port 5984 and run CouchDB manually using a shell script, we will discuss it later in this article. 
I actually don't know what's happening in line 30 obviously it is appending the sources.list file, that's all I know about that line. 
Here we install all the dependents for CouchDB such as erlang which is the backbone of CouchDB. apt-utils installed due to an error which I faced during the development if you remove it you might get the same error in the docker build. 

Line 40 is a important milestone since here you have to choose which CouchDB version you going to use and which mirror you going to use to download it. Therefore you need to check by going to the CouchDB what is the most preferred download mirror for you. If you want to download the 1.6.1 which is the latest version use this link and copy and paste the suggested mirror site in the line 40. 
In line 42 docker build function will copy the downloaded apache-couchdb-1.6.1.tar.gz file to /tmp directory and will extract it there. As we discussed earlier line 43 again download the make command to make the CouchDB in that directory. End of the make install process you are good to start CouchDB. In line 46, I'm binding my localhost IP and the default port to run CouchDB to do this I needed to edit /usr/local/etc/couchdb/local.d/docker.ini. 
Next part is pretty straightforward installing most wanted UNIX commands.
RUN apt-get install -y apache2
RUN apt-get install -q -y zip unzip
RUN apt-get install -q -y curl facter nano vim  
RUN apt-get install -q -y telnet iputils-ping curl

These commands will be highly useful in the ssh login to the instance. Therefore add any   other wanted commands. I used above shell commands throughout the testing phase. This way developer save some time during the testing phase.
Now let’s install java and Apache Startos agent

ADD packs/jdk1.6.0_24.tar.gz /opt/
RUN ln -s /opt/jdk1.6.0_24 /opt/java
ADD packs/apache-stratos-cartridge-agent-4.0.0.tgz /mnt/

This only takes 3 lines, in line 69 and 71 extracted versions of jdk1.6.0_24 and apache-stratos-cartridge-agent-4.0.0 will be pasted on opt and mnt directory respectively. ln in line 70 will copy the content in jdk1.6.0_24 to /opt/java folder.
We need to install ActiveMQ which is the communication media for Apache Stratos agent and within the docker instance. Actually activeMQ comes with Stratos agent and might be using older versions of it, therefore we need to download the latest stuff and paste those files in the packs/activem directory. 
Here is the list of files we need :

activemq-broker-5.10.0.jar
activemq-client-5.10.0.jar
geronimo-j2ee-management_1.1_spec-1.0.1.jar
geronimo-jms_1.1_spec-1.1.1.jar
hawtbuf-1.10.jar
Now let's copy these files to docker instance.

ADD packs/activemq/activemq-broker-5.10.0.jar /mnt/apache-stratos-cartridge-agent-4.0.0/lib/activemq-broker-5.10.0.jar
ADD packs/activemq/activemq-client-5.10.0.jar /mnt/apache-stratos-cartridge-agent-4.0.0/lib/activemq-client-5.10.0.jar
ADD packs/activemq/geronimo-j2ee-management_1.1_spec-1.0.1.jar /mnt/apache-stratos-cartridge-agent-4.0.0/lib/geronimo-j2ee-management_1.1_spec-1.0.1.jar
ADD packs/activemq/geronimo-jms_1.1_spec-1.1.1.jar /mnt/apache-stratos-cartridge-agent-4.0.0/lib/geronimo-jms_1.1_spec-1.1.1.jar
ADD packs/activemq/hawtbuf-1.10.jar /mnt/apache-stratos-cartridge-agent-4.0.0/lib/hawtbuf-1.10.jar


This ADD only copy and paste the LHS file to RHS directory according to the given file name in RHS.
Note: Remove above activemq files from the Dockerfile if you’re using the latest version of Stratos agent.
Now we need to configure Stratos agent by giving the correct values for:
-Dmb.ip=MB_IP
-Dmb.port=MB_PORT
-Dthrift.receiver.ip=CEP_IP
-Dthrift.receiver.port=CEP_PORT
Which is in stratos.sh file located in the in the extracted apache-stratos-cartridge-agent-4.0.0  bin directory. We are going to input those configuration details in our stratos cartridge configuration json which is included in my git repo
Note: Please left a comment in the end of the article if you have problems so far or just ask it in stackoverflow and paste the link in the comment section. 
Now we need to include some shell scripts which we are talk more in the later part of this article. Here is the list of shell scripts that we are going to use: 

init.sh
metadata_svc_bugfix.sh
run_scripts.sh
stratos_sendinfo.rb
couch_apps.sh

In the next code segemt I'm just making new directories and copy paste above scripts in /root/bin/, /usr/lib/ruby/1.8/facter//usr/local/bin/ and /usr/local/bin/ directories. 
Some lines is for giving the rights to execute shell scripts. Please google chmod for more information. In this section I’m calling the shell script that creates all the dependencies needed by 3rd party applications. For example I'm creating folder structure needed for kleks in my docker file you can add files you wanted to add in this section.
RUN mkdir /root/bin
ADD scripts/init.sh /root/bin/
RUN chmod +x /root/bin/init.sh
ADD scripts/stratos_sendinfo.rb /usr/lib/ruby/1.8/facter/
ADD scripts/metadata_svc_bugfix.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/metadata_svc_bugfix.sh
ADD scripts/run_scripts.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/run_scripts.sh

Let's move to the next section which is added to fix an error related to host entries. (I have no idea why I add these files therefore I'm going to skip this part for now)
RUN mkdir p - /root/lib
RUN cp /lib/x86_64-linux-gnu/libnss_files.so.2 /root/lib
RUN perl -pi -e 's:/etc/hosts:/tmp/hosts:g' /root/lib/libnss_files.so.2
RUN cp /etc/hosts /tmp/hosts
ENV LD_LIBRARY_PATH /root/lib
RUN locale-gen en_US.UTF-8

Will explain this later.
Now let's expose the port which simply means that open the port for public use. This is a vulnerability which can't be tolerated.
EXPOSE 22
EXPOSE 80
EXPOSE 5984

For example now only (after EXPOSE) you can access above ports publicly.

And last but not least the entry point for all of the shell scripts we included. This is the starting point of the execution of shell script code.
ENTRYPOINT /usr/local/bin/run_scripts.sh | /usr/sbin/sshd -D

Above code means that just after stratos trying to spawn an instance execution within the docker image will start from here, there for this will be the controller shell script. Let’s start off with run_scripts.sh.
#!/bin/bash
LOG=/tmp/run-script.log
echo "run_script started..." >> $LOG

echo "Starting /usr/local/bin/metadata_svc_bugfix.sh" >> $LOG
/usr/local/bin/metadata_svc_bugfix.sh
echo "Ending /usr/local/bin/metadata_svc_bugfix.sh" >> $LOG

echo "Starting /etc/init.d/apache2 start" >> $LOG
/etc/init.d/apache2 start > /dev/null 2>&1 &
echo "Ending /etc/init.d/apache2 start" >> $LOG

echo "Starting /root/bin/init.sh" >> $LOG
/root/bin/init.sh > /tmp/init.log
echo "Ending /root/bin/init.sh" >> $LOG

echo "Starting /usr/local/bin/couchdb" >> $LOG
/usr/local/bin/couchdb couchdb > /dev/null 2>&1 &
echo "Ending /usr/local/bin/couchdb" >> $LOG

echo "Starting /usr/local/bin/couch_apps.sh" >> $LOG
/usr/local/bin/couch_apps.sh > /dev/null 2>&1 &
echo "Ending /usr/local/bin/couch_apps.sh" >> $LOG

echo "Ending run_script..." >> $LOG
echo " " >> $LOG

Above run_scripts.sh that I have created for CouchDB. I added lot of echo's for debugging purposes. Altogether this script is initiating 2 scripts (metadata_svc_bugfix.sh, init.sh) and 2 other processes which is apache server and CouchDB. You can find the log file of the run script in /tmp/run-script.log.
It is in vain to describe each and every stuff here onward if you reach this far that means your software developer that has a basic knowledge about scripting. But those who are don't know, I would like to explain some of it.

Let’s start with /dev/null 2>&1 & this UNIX command is used to run the process as a background process and don't keep a track of it. You might think what are these arrows are doing simply single arror(>) will create a new file or a instance as per the RHS and the double arrows (>>) will append the exiting file which is in RHS.

Moving forward metadata_svc_bugfix.sh is an essential bug fix that I'm going to need for this version of Apache Startos agent. Hopefully you can remove this bug fix (simply remove the line from the run_scripts.sh) when using the future releases of Stratos agent.

Now we can start the apache2 server using this run script now it's time to set the Stratos agent configuration which is done using init.sh. In the dockerfile that we discuss earlier we copied and pasted this file in to specific directory.

#!/bin/bash
# --------------------------------------------------------------

#
# or more contributor license agreements.  See the NOTICE file
# regarding copyright ownership.  The ASF licenses this file

# distributed with this work for additional information
# to you under the Apache License, Version 2.0 (the
# with the License.  You may obtain a copy of the License at

# "License"); you may not use this file except in compliance
#
#  http://www.apache.org/licenses/LICENSE-2.0
#
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# --------------------------------------------------------------

# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#


FIND=`which find`

perl -pi -e 's:/etc/hosts:/tmp/hosts:g' /root/lib/libnss_files.so.2
export LD_LIBRARY_PATH=/root/lib
echo $LD_LIBRARY_PATH

MKDIR=`which mkdir`
UNZIP=`which unzip`
ECHO=`which echo`
GREP=`which grep`
#PUPPETD=`which puppet`

RM=`which rm`
XARGS=`which xargs`
SED=`which sed`
CUT=`which cut`
AWK=`which awk`
IFCONFIG=`which ifconfig`
HOSTNAME=`which hostname`
SLEEP=`which sleep`
TR=`which tr`
HEAD=`which head`
WGET=`which wget`
#echo "started creating launch-params and stratos.sh agent " >> $LOG

AGENT="agent"
PUPPETAGENT="${PUPPETD} ${AGENT}"

#COMMAND="${PUPPETAGENT} -vt"
IP=`${IFCONFIG} eth0 | ${GREP} -e "inet addr" | ${AWK} '{print $2}' | ${CUT} -d ':' -f 2`
LOG=/tmp/init-script.log

HOSTSFILE=/tmp/hosts
   if [ ! -f public-ipv4 ]

HOSTNAMEFILE=/etc/hostname
#PUPPETCONF=/etc/puppet/puppet.conf

#read_master() {
# ${COMMAND}
#}


is_public_ip_assigned() {

while true
do
   wget http://169.254.169.254/latest/meta-data/public-ipv4
     then
       read -r ip<public-ipv4;

       echo "Public ipv4 file not found. Sleep and retry" >> $LOG
       sleep 2;
       continue;
     else
       echo "public-ipv4 file is available. Read value" >> $LOG
       # Here means file is available. Read the file
       echo "value is **[$ip]** " >> $LOG

            break

       if [ -z "$ip" ]
         then
           echo "File is empty. Retry...." >> $LOG
           sleep 2
           rm public-ipv4
           continue
          else
            echo "public ip is assigned. value is [$ip]. Remove file" >> $LOG
            rm public-ipv4
          fi
 ${WGET} http://169.254.169.254/latest/user-data -O /tmp/payload/launch-params

     fi
done
}


DATE=`date +%d%m%y%S`
RANDOMNUMBER="`${TR} -c -d 0-9 < /dev/urandom | ${HEAD} -c 4`${DATE}"

if [ ! -d /tmp/payload ]; then

 ## Check whether the public ip is assigned
 is_public_ip_assigned

 echo "Public ip have assigned. Continue.." >> $LOG

 ${MKDIR} -p /tmp/payload
 INSTANCE_HOSTNAME=`sed 's/,/\n/g' launch-params | grep HOSTNAME | cut -d "=" -f 2`

 cp /tmp/payload/launch-params /mnt/apache-stratos-cartridge-agent-4.0.0/payload/launch-params

 cd /tmp/payload
 SERVICE_NAME=`sed 's/,/\n/g' launch-params | grep SERVICE_NAME | cut -d "=" -f 2`
 DEPLOYMENT=`sed 's/,/\n/g' launch-params | grep DEPLOYMENT | cut -d "=" -f 2`

 #get user parameters
        CEP_IP=`sed 's/,/\n/g' launch-params | grep CEP_IP | cut -d "=" -f 2`
        echo 'MB_IP - '.$MB_IP >> $LOG

        CEP_PORT=`sed 's/,/\n/g' launch-params | grep CEP_PORT | cut -d "=" -f 2`
        MB_IP=`sed 's/,/\n/g' launch-params | grep MB_IP | cut -d "=" -f 2`
        MB_PORT=`sed 's/,/\n/g' launch-params | grep MB_PORT | cut -d "=" -f 2`

        # print the collected data
        echo 'CEP_IP - '.$CEP_IP >> $LOG
        echo 'CEP_PORT - '.$CEP_PORT >> $LOG
        echo 'MB_PORT - '.$MB_PORT >> $LOG

        #replace variables with user parameters
        sed -i "s/MB_IP/$MB_IP/g" jndi.properties.template

        cd /mnt/apache-stratos-cartridge-agent-4.0.0/bin/
        sed -i "s/MB_IP/$MB_IP/g" stratos.sh
        sed -i "s/MB_PORT/$MB_PORT/g" stratos.sh
        sed -i "s/CEP_IP/$CEP_IP/g" stratos.sh
        sed -i "s/CEP_PORT/$CEP_PORT/g" stratos.sh

        cd /mnt/apache-stratos-cartridge-agent-4.0.0/conf
        sed -i "s/MB_IP/$MB_IP/g" jndi.properties
        sed -i "s/MB_PORT/$MB_PORT/g" jndi.properties

        cd templates
        sed -i "s/MB_PORT/$MB_PORT/g" jndi.properties.template



 #start stratos agent
 cd /mnt/apache-stratos-cartridge-agent-4.0.0/bin/;./stratos.sh > /tmp/agent.log
 echo "agent started..." >> $LOG
fi

# END



This script will fetch details from launch-params file which is located in /tmp/payload/launch-params, and then paste these extracted values into stratos.sh file located in /mnt/apache-stratos-cartridge-agent-4.0.0/bin/. Other than that if you look closely this script we are updating 2 other configuration files as well which is jndi.properties.template and jndi.properties.

Now you’re done with the agent configuration, it’s time to run CouchDB in your Stratos docker instance. which is simply done using  /usr/local/bin/couchdb couchdb > /dev/null 2>&1 & line of code.

Last we need to implement 3rd party dependencies that user (developer) need to use.
couch_apps.sh
#######################
# Adding dependencies for couchApps
#######################
# for Kleks CMS
touch /usr/local/var/lib/couchdb/kleks.couch
touch /usr/local/var/lib/couchdb/static.couch

That’s the end of the long and boring how to develop a Stratos docker couchdb.
Note: Please left any questions in the comment section or just ask it in stackoverflow and paste the link in the comment section. 

Will talk about how to configure CouchDB cartridge for use in Stratos in the next article.

Thanks and have nice day!



Comments

  1. This blog is nice and very informative. I like this blog.
    blog Please keep it up.

    ReplyDelete

Post a Comment