Creating Compute Engine instance

  1. In the Google Cloud Developer console I went to Compute Engine in the left sliding pan and go to VM instances page.
  2. In the Boot disk section, I choose the Ubuntu 16.04 LTS amd64 xenial image.
  3. In the Machine type I selected small (1 shared vCPU) and Click Select.
  4. In the Firewall section, I selected Allow HTTP traffic and Allow HTTPS traffic.
  5. Finally I created the instance by clicking the Create button to create the instance.

Installing Jenkins

I SSH into the instance and Run the following commands in the in the terminal,
wget -q -O - https://pkg.jenkins.io/debian/jenkins.io.key | sudo apt-key add -
sudo sh -c 'echo deb http://pkg.jenkins.io/debian-stable binary/ > /etc/apt/sources.list.d/jenkins.list'
sudo apt-get update
sudo apt-get install jenkins
sudo systemctl start jenkins
To set up our installation, I visit Jenkins on its default port, 8080, using the server domain name or IP address: http://ip_address_or_domain_name:8080. We should see “Unlock Jenkins” screen, which displays the location of the initial password. In the terminal window, I used the cat command to display the password:
sudo cat /var/lib/jenkins/secrets/initialAdminPassword
I copied the 32-character alphanumeric password from the terminal and pasted it into the “Administrator password” field, then clicked “Continue”. The next screen presents the option of installing suggested plugins or selecting specific plugins. The default Jenkins server is NOT encrypted, so the data submitted with this form is not protected. So, I will continue as Admin and create an user later when I will setup SSL.

Installing Nginx and Map the Domain

  1. Run the following commands to install Nginx
    sudo apt-get update
    sudo apt-get install nginx
  2. In the Google Cloud Console, I went to External IP section under VPC Network the Reserve a static address page in the GCP Console.
  3. I Changed the IP type from Ephemeral to Static and Choose a name
  4. Click Reserve to reserve the IP.
  5. In the DNS settings Add the instance external IP as ‘A record’ and add ‘www’ as CNAME for the Domain
  6. Open /etc/nginx/sites-available/default, with nano or your favorite text editor by the following command in the terminal, sudo nano /etc/nginx/sites-available/default
  7. Find the existing server_name line and replace the underscore, _, with your domain name: . . . server_name example.com www.example.com; . . .
  8. Save the file (Ctrl+x and then y) and quit your editor.
  9. Reload Nginx to load the new configuration by sudo systemctl reload nginx

Install Let’s encrypt SSL to serve on https

  1. From Let’s Encrypt Documentation(https://certbot.eff.org/#ubuntuxenial-nginx) we can see what are the commands we need to run
  2. I ran the following command
    sudo apt-get update
    sudo apt-get install software-properties-common
    sudo add-apt-repository ppa:certbot/certbot
    sudo apt-get update
    sudo apt-get install python-certbot-nginx
    sudo certbot --authenticator standalone --installer nginx -d knsakib.com -d www.knsakib.com --pre-hook "service nginx stop" --post-hook "service nginx start"
    sudo certbot renew --dry-run

Change the server block in Nginx to serve Jenkins under /jenkins path

  1. I Opened /etc/nginx/sites-available/default , again with nano or your favorite text editor
  2. First, I added specific access and error logs in the server block with the SSL configuration settings,
    server /{
    ...
    access_log /var/log/nginx/jenkins.access.log;
    error_log /var/log/nginx/jenkins.error.log;
    ...
    }
  3. Add a location in the default server block as follows,
    location ^~ /jenkins {
    proxy_pass http://127.0.0.1:8080;
    include /etc/nginx/proxy_params;
    proxy_read_timeout 90;
    proxy_redirect http://127.0.0.1:8080 https://knsakib.com/jenkins;
    }
  4. We can check the syntax if it is ok by sudo nginx -t In addition, we must ensure that Jenkins is configured to listen for requests to the /jenkins path. For that I added the parameter –prefix=/jenkins to the Jenkins default startup configuration file which is /etc/default/jenkins. We will modify the /etc/default/jenkins configuration file to make these adjustments, Locate the JENKINS_ARGS line and add --prefix=/jenkins to the existing arguments:
    . . .
    JENKINS_ARGS="--webroot=/var/cache/$NAME/war --httpPort=$HTTP_PORT --prefix=/jenkins"
  5. To use the new configuration settings, we’ll restart Jenkins and Nginx.
    $sudo systemctl restart jenkins

    $sudo systemctl restart nginx
    If there is any issue to restart we can check the status by sudo systemctl status nginx
  6. For Jenkins to work with Nginx, we need to update the Jenkins configuration so that the Jenkins server listens only on the localhost interface rather than all interfaces (0.0.0.0). If Jenkins listens on all interfaces, then it’s potentially accessible on its original, unencrypted port (8080). We will modify the /etc/default/jenkins configuration file to make these adjustments.
    sudo nano /etc/default/jenkins 
    Locate the JENKINS_ARGS line and add –httpListenAddress=127.0.0.1 to the existing arguments:
    . . .
    JENKINS_ARGS="--webroot=/var/cache/$NAME/war --httpPort=$HTTP_PORT --httpListenAddress=127.0.0.1"
  7. Save and exit the file. To use the new configuration settings, we’ll restart Jenkins and Nginx.
    sudo systemctl restart jenkins

Create a New Admin User

  1. Enable Sign UP new user by changing the file /var/lib/jenkins/config.xml
    sudo nano /var/lib/jenkins/config.xml
    Set true to false
    sudo systemctl restart jenkins
  2. Sign Up new user
  3. Change the /var/lib/jenkins/config.xml file again and set true again
    sudo systemctl restart jenkins

Install Docker

  1. First, add the GPG key for the official Docker repository to the system: curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add –
  2. Add the Docker repository to APT sources:
    sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
  3. Next, update the package database with the Docker packages from the newly added repo:
    sudo apt-get update
  4. If you want to avoid typing sudo whenever you run the docker command, add your username to the docker group:
    sudo usermod -aG docker ${USER}
  5. After following the prerequisites, both Jenkins and Docker are installed on your server. However, by default, the Linux user responsible for running the Jenkins process cannot access Docker. To fix this, we need to add the jenkins user to the docker group using the usermod command:
    sudo usermod -aG docker jenkins
    sudo systemctl restart jenkins

Redirecting direct access to site from server’s IP

  1. For this I created a new site that responds to the IP and responds with a 301 redirect. To create a site just go to your Nginx’s installation /sites-availabledirectory. This directory is commonly located at /etc/nginx/sites-available, run in your terminal:
    cd /etc/nginx/sites-available
  2. Once in the directory create a new server config file or edit one of the server files that you have there. For this example let’s create a new one and add the server block there, I’ll use nano to edit the file, run in your terminal:
    touch empty_site
    nano empty_site
  3. Add this server block, make sure to replace server_ip with your server’s IP.
    server {
    listen 80;
    listen 443; # add this to block HTTPS access
    server_name my_server_ip_adress https://my_server_ip_adress;
    return 301 https://knsakib.com;
    }
  4. Save and exit the file.
  5. Now we just have to enabled the site and restart the Nginx server. To enable the site lets navigate to the /sites-enabled directory inside the Nginx config and create a symlink for the server config file that we previously created, run in your terminal:
    cd /etc/nginx/sites-enabled
    ln -s /etc/nginx/sites-available/empty_site empty_site
  6. Once this is done, we just have to restart the nginx server and everything should be working as expected, to restart run this:
    sudo service nginx restart

Rendering my react site ’cause it’s a Blog and it needs SEO

  1. I used cra-universal cra-universal to rendered my create-react-app blog. It uses express server render the static react app. It biulds the react site and biuld the rendered site together. It is an amazing tool, thanks to it’s author. All the intruction are mentioned at author’s github page.
  2. Because I am using react router, I made sure that App.js didn’t render BrowserRouter, but put it on src/index.js or outer files The index.js code is here. And the App.js is here.
  3. I also linked bootstarp cdn style sheet in index.html in stead of using any bootstrap npm module to make sure that the css will not create any issue with rendering.

Creating Dockerfile

  1. I created the docker file in my project directory, and add the image I want to inherit from, and override the log level setting. It includes all the module installation command and cra-universal build commands that include both the react build and server biuld command.
  2. I can just run npm install in my Dockerfile to install dependencies and copied from the build host, However, if I do that, there’s no reason to copy the full node_modules directory over at all. That’s where a .dockerignore file comes in. This lets me filter which files the Docker CLI sends to the Docker daemon, which is great for our efficiency!
  3. Also note that create-react-app will generate a non-shrinkwrappable dependency tree, so I cleaned it up first. and used a –dev shrinkwrap.
    npm prune
    npm dedupe
    npm install
    npm shrinkwrap --dev
    There is a good explanation of these steps mentioned herefor building a dockerized production build react app.
  4. Finally my Dockerfile looks like this.

Creating Automatic build pipeline in jenkins

With the help of steps described in “Install Docker” section above and some of the default plugins we enabled during installation, Jenkins can now use Docker to run build and test tasks. Now I want Jenkins to build and run my docker image as soon I push code to my github repository.

  1. In order for Jenkins to triger the build I need it to watch my GitHub projects. For this I created a Personal Access Token in our GitHub account. I clicked on my user icon in the upper-right hand corner and select Settings from the drop down menu.
  2. I locate the Developer settings section of the left-hand menu and clicked Personal access tokens.
  3. Then I clicked on Generate new token button
  4. In the Token description box, I added a description
  5. In the Select scopes section, I checked the repo:status, repo:public_repo and admin:org_hook boxes. These will allow Jenkins to update commit statuses and to create webhooks for the project. If I used a private repository, I would need to select the general repo permission instead of the repo subitems.
  6. Then I clicked Generate token at the bottom.
  7. My new token was displayed and copied the token. Please note that as the message indicates on that page, there is no way to retrieve the token once you leave this page.
  8. From my Jenkins web interface in the the main dashboard, I clicked Credentials in the left hand menu.
  9. On the next page, I clicked the arrow next to (global) within the Jenkins scope. In the box that appears, I clicked Add credentials
  10. Under the Kind drop down menu, I selected Secret text. In the Secret field, I pasted my GitHub personal access token. Filled out the Description field. I left the Scope as Global and the ID field blank
  11. Clicked the OK button to finish
  12. In the main Jenkins dashboard, I clicked Manage Jenkins in the left hand menu and in the list of links on the following page, I clicked Configure System.
  13. In the the GitHub section of that page, I clicked the Add GitHub Server button and then select GitHub Server:
  14. The section will expand to prompt for some additional information. In the Credentials drop down menu, I selected my GitHub personal access token that I added in the steps above
  15. I Tested connection so that Jenkins can make a test API call to my account and verify connectivity. Lastly I clicked the Save button to implement your changes
  16. I created the Jenkinsfile which is not too much complicated. It just conatins different stages from cloning the repo to build and run the docker image. Jenkin is running this deamon as a node.
  17. Now in the main Jenkins dashboard, I clicked New Item in the left hand menu.
  18. I named the new pipeline and select Pipeline as the item type.
  19. Click the OK and on the next screen, checked the GitHub project box. In the Project url field that appears, I pasted my project’s GitHub repository URL.
  20. Next, in the Build Triggers section, I checked the GitHub hook trigger for GITScm polling box. In the Pipeline section, I need to tell Jenkins to run the pipeline defined in the Jenkinsfile in our repository. I changed the Definition type to Pipeline script from SCM.
  21. In the new section that appears, I chose Git in the SCM menu. In the Repository URL field that appears, I entered the URL of the repository again.
  22. If the project is a private Repository, I will use the add credentials button to add additional access to the repository. I can add a personal access token as I did with the hooks configuration earlier. When I am finished, I clicked the Save button at the bottom of the page.
  23. In order to trigger Jenkins to set up the appropriate hooks, we need to perform a manual build the first time. In pipeline’s main page, I clicked Build Now in the left hand menu.
  24. A new build will be scheduled. In the Build History box in the lower left corner, a new build should appear in a moment. Additionally, a Stage View will begin to be drawn in the main area of the interface. This will track the progress of your testing run as the different stages are completed.
  25. In the Build History box, click on the number associated with the build to go to the build detail page. From here, you can click the Console Output button in the left hand menu to see details of the steps that were run.
  26. I Clicked ‘Back to Project’ in the left hand menu when finished in order to return to the main pipeline view. Now that we’ve built the project once, we can have Jenkins create the webhooks for our project. Click Configure in the left hand menu of the pipeline.
  27. No changes are necessary on this screen, just click the Save button at the bottom. Now that the Jenkins has information about the project from the initial build process, it will register a webhook with our GitHub project when I saves the page.
  28. We can verify this by going to your GitHub repository and clicking the Settings button.
  29. On the next page, if we click Webhooks from the side menu. You should see your Jenkins server webhook in the main interface. Now, when you push new changes to your repository, Jenkins will be notified. It will then pull the new code and retest it using the same procedure.

These steps are very well defined with graphical representation in this Digital Ocean Tutorial.

Maintenance

As I am using bare-bone instance to run my Docker, I may need to sometimes remove the previous Docker image to clear up my memory. To do this, we need to run
docker volume ls -qf dangling=true | xargs -r docker volume rm
There is a good doc expaining how to claim volume for different docker version. I can include this command in Jenkinsfile to automate this process But I just want make the process resource intensive or to take more time. So, I will run this command once in a while in the instance to clean up the memory.