DevOps - ILLYAKO/mywiki GitHub Wiki

DevOps foundation

Docker, Docker Compose

Test (Unit test and Integration test)

RSpec, Capybara, Selenium

Deploy

Terraform

CI/CD Pipeline (build, test, deploy)

Jenkins


1. Docker

Docker is a platform for running applications and their dependencies in isolated environments called containers, on nearly any operating system. Homebrew (brew.sh)for installing applications on Mac(Linux) Aptitude, YUM, and YaST are package managers that let you install anything on the Mac

1.1. Docker Installation

1.1.1. Install Homebrew on Linux

$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

1.1.2. Install Docker on Linux

$ brew install cask docker - cask(third-part repository) installs Docker, Docker CLI, Docker Compose

1.1.4. Run Docker Desktop (Windows)

1.1.5. Test Docker

$ docker run hello-world

The Dockerfile is a manifest that describes the image that the container will use when we run it. All of the configuration dependencies and environment dependencies and everything that the application needs will be expressed within the Dockerfile.
Docker will do a few things.

  1. The Docker reads and parses the Dockerfile.
  2. The Docker fetches the parent image that this image is going to use. If there is no parent image that you're going to use, you would start from scratch.
  3. The Docker runs any commands, within the Dockerfile that is on top of that image, lastly if defined you can set a process that runs whenever a container from that image is spun up.

1.2.1. Docker Hub is a repository of docker images.

The 'alpine' is the smallest version of the image

1.2.2. Dockerfiles lines:

FROM nginx:alpine                                   #parent image:tag

MAINTAINER Illya Korotun <[email protected]>  #author
COPY website /website                               #copy context into the image host directory to the container directory
COPY nginx.conf /etc/nginx/nginx.cong               #copy the config file to the container

EXPLOSE 80 #we want to map one(80) of the ports on my Mac to the port on the container.

1.2.3. Build docker image

$ docker build --tag website . # create an image with tag/name "website" from "." (current directory)

1.2.4. Run Container

$ docker run --publish 80:80 website # publish on ports HOST:Container

1.3. Docker Compose manifest

Docker compose is a simple and lightweight platform for running multiple container applications in a single stack and creating a network.

1.3.1 docker-compose.yml
version: '3.7'   # docker compose version
services:
  website:       # service name usually equals the container name
    build:       # look for Dockerfile and build an image
      context: . # Dockerfile address "."-current directory
    ports:       # container ports HOST:Container
      - 80:80    # Dockerfile address "."-current directory
1.3.2 Run docker-comose and build container

$ docker-compose run website

1.3.3 Up docker-comose and build container and make port mapping

$ docker-compose up

1.3.4 Show a list of the docker-compose container running in

$ docker-compose ps

1.3.5 Show a list of the docker container running

$ docker ps

1.3.6 Stop the running container and remove the network

$ docker-compose down


2. Testing App with RSpec, Capybara, and Selenium

  • RSpec is a Ruby-based testing framework. RSpec is looking for a folder called Spes and _spec.rb files (ex. page_spec.rb).
  • Capybara is a tool that lets you use a web driver to create a browser and interact with the website. It gets the HTML of a website, run Javascript, and tests things like a button looking like it should look.
  • The web driver, which is what spins up the browser and runs tests against it.
  • Rack Test is a very simple web browser that just renders HTML and tests it, but it can't do more complicated things like running Javascript or computing CSS.
  • Selenium is a web driver that spins up a real web browser and tests against whatever the web browser sees, which is nice because our website does have computed CSS, and spinning up a real web browser and getting the results of that computed CSS.

2.1. RSpec

2.1.1. Create new directories 'spec' and 'spec/unit' in the project root directory

$ mkdir spec
$ mkdir spec/unit

2.1.2. Create a new file page_spec.rb

$ touch spec/unit/page_spec.rb

# spec/unit/page_spec.rb
require 'capybara'
require 'capybara/dsl'

describe "Example page render unit tests" do
  it "Should show the Explore California logo" do
  end
end

2.1.3 Edit docker-compose adding the 'unit-test'

version: "3.7"
services:
  website:
    build:
      context: .
    ports:
      - 80:80
  unit-test:                       # new service
    volumes:                       # mount the existing volume to the container
      - "$PWD:/app"                #current directory to /app
    build:                         # build a new image
      context: .                   #current context
      dockerfile: rspec.dockerfile #spesific dockerfile
    command:
      - --pattern                  # entrypoint option
      - /app/spec/unit/*_spec.rb        # entrypoint option test target
    

2.1.4 Create new file rspec.dockerfile

$ touch rspec.dockerfile

# create from base ruby image
FROM ruby:alpine 
MAINTAINER Illy Korotun <[email protected]>

# apk is pacage from alpine
# add package ruby-nokogiri for parsing 
RUN apk add build-base ruby-nokogiri

# install in the container from the ruby gem library rubygems.org
RUN gem install rspec capybara selenium-webdriver

#Which process should start when the container is starting. Use double equate 
ENTRYPOINT ["rspec"]

Two way say container how to run CMD and ENTRYPOINT
Every container start with the default command #/bin/sh -c
CMD ["rspec"] # is mean #/bin/sh -c "rspec"

ENTRYPOINT is running the command where it placed
ENTRYPOINT ['option1', 'option2'] # is mean docker run this_image --option1 --option2
ENTRYPOINT ['rspec'] # is mean docker run this_image rspec

2.1.5 Execute docker-compose -d to hide the output of the container

$ docker-compose up -d website

2.1.6 Run container unit-test with the option to remove the container after use

$ docker-compose run --rm unit-test
The dot '.' should be shown as a result of the successful test

2.1.7 Docker Compose config file

$ docker-compose config

2.1.8 Run container 'website' in the background

$ docker-compose up -d website

2.1.9 Add tests

# spec/unit/page_spec.rb
require 'capybara'
require 'capybara/dsl'
require 'selenium-webdriver'

include Capybara::DSL
Capybara.app_host = "http://website" # Using Selenium; connect over network
Capybara.run_server = false # Disable Rack since we are using Selenium.
Capybara.register_driver :selenium do |app|
  Capybara::Selenium::Driver.new(
    app,
    browser: :remote,
    url: "http://#{ENV['SELENIUM_HOST']}:#{ENV['SELENIUM_PORT']}/wd/hub",
    capabilities: Selenium::WebDriver::Remote::Capabilities.chrome( # desired_capabilities was replaced by capabilities in new Capybara 4
      "chromeOptions" => {
        "args" => ['--no-default-browser-check']
      }
    )
  )
end
Capybara.default_driver = :selenium

describe "Example page render unit tests" do
  it "Shows the Explore California logo" do
    visit('/')
    expect(page.has_selector? '.logo').to be true
  end
end

2.1.10 Add Selenium to docker-compose.yml

version: "3.7"
services:
  selenium:
    image: selenium/standalone-chrome-debug # debug version include VNC
    ports:
      - 4444:4444
      - 5901:5900 # VNC ports. The 5900 is the default VNC port
  unit-test: # new service
    build: # build a new image
      dockerfile: rspec.dockerfile #spesific dockerfile
      context: . #current context
    volumes: # mount the existing volume to the container
      - $PWD:/app #current directory to /app
    command:
      - --pattern # entrypoint option
      - /app/spec/unit/*_spec.rb # entrypoint option test target
  website:
    build:
      context: .
    ports:
      - 80:80

2.1.11 Run service website selenium and Run container unit-tests

$ docker-compose up -d website selenium
$ docker-compose run --rm unit-test

2.2.1 Open VNC (VNC Viewer for Windows)

localhost:5901
password: secret

2.1.13 Run container unit-tests

$ docker-compose run --rm unit-test


3. Infrastructure as Code with Terraform

Terraform is an open-source infrastructure-as-configuration software tool created by HashiCorp. Users define and provide data center infrastructure using a declarative configuration language known as HashiCorp Configuration Language, or optionally JSON. the file is saved in .terraform/terraform.tfstate Terraform backend is usually stored in AWS S3 Terraform plugins which created by providers CA certificate add CA-certificate in docker file

3.1 Create Terraform Dockerfile

# create from Linux alpine
FROM alpine
MAINTAINER Illya Korotun <[email protected]>

#download Terraform from https://developer.hashicorp.com/terraform/downloads

RUN wget -O /tmp/terraform.zip https://releases.hashicorp.com/terraform/0.12.12/terraform_0.12.12_linux_amd64.zip
RUN unzip /tmp/terraform.zip -d /

# add CA-certificate
RUN apk add --no-cache ca-certificates curl

# nobody user has privileges to execute
USER nobody 

# check installed terraform in the container
ENTRYPOINT [ "/terraform" ]

3.2 Build Terraform image

$ docker build -t terraform . -f terraform.Dockerfile

3.3 Run container and remove(--rm) it when it done, using interactive command to use shell(--interactive) and shell session(--tty), and change entrypoint for container (sh terraform)

$ docker run --rm --interactive --tty --entrypoint sh terraform
~$ /terraform #check installed terraform in the container
~$ exit

3.4 Build Terraform docker image with name and source file and temporary working directory - context ($PWS or dot ".")(same directory)

docker build --tag terraform --file terraform.dockerfile .

3.5 Run Terraform docker container and remove it and show the version

docker run --rm terraform -version

3.6 Add Terraform to docker-compose

...
service:
  terraform:
    build:
      dockerfile: terraform.dockerfile
      context: .
    environment:
      AWS_ACCESS_KEY_ID: "${AWS_ACCESS_KEY_ID}"
      AWS_SECRET_ACCESS_KEY: "${AWS_SECRET_ACCESS_KEY}"
      AWS_REGION: "${AWS_REGION}"
    volumes:
      - $PWD:/app
    working_dir: /app
...

3.6.1 Run Terraform docker-compose

$ docker-compose run --rm terraform

AWS - Amazon Web Service is a cloud provider of resources for Amazon data center.
Elastic Cloud Compute or EC2 allows the creation of virtual machines.
Amazon S3 is a simple storage service.
The project will be copied into the bucket of the S3.

3.8 Terraform (terraform.io) configuration file main.tf is the Terraform entry point.

data "aws_iam_policy_document" "bucket_policy" {
  statement {
    sid = "PublicReadGetObject"
    effect = "Allow"
    actions = [ "s3:GetObject" ]
    principals {
      type = "*"
      identifiers = [ "*" ]
    }
    resources = [ "arn:aws:s3:::explorecalifornia.org/*" ]
  }
}

/* We can access properties from data sources using this format:
   ${data.<data_source_type>.<data_source_name>.<property>.

   In this case, we need the JSON document, which the documentation
   says can be accessed from the .json property. */

resource "aws_s3_bucket" "website" {
  bucket = "explorecalifornia.org"  // The name of the bucket. It should be unique. 
  acl    = "public-read"            /* Access control list for the bucket.
                                       Websites need to be publicly-available
                                       to the Internet for website hosting to
                                       work. */
  policy = "${data.aws_iam_policy_document.bucket_policy.json}"
  website {
    index_document = "index.htm"   // The root of the website.
    error_document = "error.htm"   // The page to show when people hit invalid pages.
  }
}

output "website_bucket_url" {
  value = "${aws_s3_bucket.website.website_endpoint}"
}

The terraform state should be removed from the public repository.

3.9. Init Terraform with docker-compose

docker-compose run --rm terraform init

3.10 Run Terraform plan

docker-compose run --rm terraform plan
Enter a value: us-east1 // closest server location

3.11 Run Terraform apply

docker-compose run --rm terraform apply

3.12 Add AWS in docker-compose

services:
...
  aws:
    image: organs/awscli
    environment:
      AWS_ACCESS_KEY_ID: "${AWS_ACCESS_KEY_ID}"
      AWS_SECRET_ACCESS_KEY: "${AWS_SECRET_ACCESS_KEY}"
      AWS_REGION: "${AWS_REGION}"
      AWS_DEFAULT_REGION: us-west-2
    volumes:
      - $PWD:/app
    working_dir: /app
...

3.12.1. Run AWS CLI

The default entrypoint 'sh' $ docker-compose run --rm aws

3.12.2. Run AWS CLI and see the EC2

$ docker-compose run --rm --entrypoint aws aws ec2 describe-instances

3.12.3. Run AWS CLI and get the URL of the buckets

$ docker-compose run -rm terraform output

3.12.4. Run AWS CLI and copy local to buckets (Deploy)

$ docker-compose run -rm --entrypoint aws aws s3 cp --recursive website/ s3://explorecalifornia.org

3.12.5. Run AWS CLI and See what is in the buckets

$ docker-compose run -rm --entrypoint aws aws s3 ls s3://explorecalifornia.org*

3.12.6. Run AWS CLI and Delete the buckets

$ docker-compose run -rm --entrypoint aws aws s3 rm s3://explorecalifornia.org --recursive

3.12.7. Run AWS CLI and Desrtoy

$ docker-compose run -rm terraform destroy

3.13. Integration test

3.13.1. Create a new directory "spec/integration" and copy the unit test

$ mkdir spec/integration $ cp spec/unit/page_spec.rb spec/integration/

3.13.2. Change 'Capybara.app_host' in the page_spec.rd

include Capybara::DSL
Capybara.app_host = "http://#{ENV['WEBSITE_URL']}"
...

3.13.3. Run Terraform

docker-compose run --rm terraform plan docker-compose run --rm terraform apply

3.13.4. Add integration test in the docker-compose

services:
...
  integration-test: # new service
    environment:
      SELENIUM_HOST: selenium
      SELENIUM_PORT: 4444
    volumes: # mount existen volume to container
      - $PWD:/app #current directorin to /app
    build: # build new image
      dockerfile: rspec.dockerfile #spesific dockerfile
      context: . #current context
    command:
      - --pattern # entrypoint option
      - /app/integration/unit/*_spec.rb # entrypoint option test target  
...

3.13.5. Run integration test

3.13.5.1. Copy to s3

$ docker-compose run -rm --entrypoint aws aws s3 cp --recursive website/ s3://explorecalifornia.org $ docker-compose up -d selenium
Start VNC session localhost:5901
$ docker-compose run --rm -e WEBSITE_URL=your_remote_site_url integration-test "." is mean that it is passing test

3.13.6. If we don't need the website it could be deleted and destroyed

3.13.6.1. Run AWS CLI and Delete the buckets

$ docker-compose run -rm --entrypoint aws aws s3 rm s3://explorecalifornia.org --recursive

3.13.6.2. Run AWS CLI and Desrtoy

$ docker-compose run -rm terraform destroy

3.13.6.3. Down Selenium container

$ docker-compose down


4. CI Jenkins on docker

4.1 Dockerized instance of Jenkins

4.1.1 Create jenkins.dockerfile

# image from hub.docker.com
FROM jenkins/jenkins:alpine
MAINTAINER Illya Korotun <[email protected]>

# Jenkins plugins on [plugins.jenkins.io](https://plugins.jenkins.io/)
COPY plugins.txt /usr/share/jenkins/plugins.txt

# Install Jenkins plugins
# RUN /usr/local/bin/install-plugins.sh < /usr/share/jenkins/plugins.txt
RUN jenkins-plugin-cli -f /usr/share/jenkins/plugins.txt

# Expose Jenkins default port
EXPOSE 8080

4.1.2 Jenkins from plugins.jenkins.io in plugins.txt

workflow-aggregator
seed
git

4.1.3 Add a new service Jenkins in docker-compose.yml

...
services:
  jenkins:
    build:
      context: .
      dockerfile: jenkins.dockerfile
    environment:
      JAVA_OPTS: "-Dhudson.plugins.git.GitSCM.ALLOW_LOCAL_CHECKOUT=true" 
    volumes:
      - $PWD/jenkins_home:/var/jenkins_home #Jenkins working directory on HOST : Container
      - $PWD:/app #current directory for app HOST:Container change to Git
    ports:
      - 8080:8080 # HOST_port:Container_port
...

4.1.4 Up Jenkins container

$ docker-compose up Jenkins

4.1.5. Open Jenkins in the browser http://localhost:8080

username: admin password in the log

Jenkins initial setup is required. An admin user has been created and a password generated.

Please use the following password to proceed with the installation:


ccc96995dba047faa4280bc57471d001


This may also be found at: /var/jenkins_home/secrets/initialAdminPassword

or in the file /var/jenkins_home/secrets/initialAdminPassword
Not commit Jenkins home to Git repository Start using Jenkins

declarative Jenkins pipeline (more readable)
scriptive Jenkins pipeline (Grovy)

pipeline {    //
  agent any  // what kind of Jenkins slaves to build this job on
  stages {  // stages define the actual stages that need to run. And these run serially by default, so they run step-by-step. You can configure it to run in parallel. 
    stage('Build our website'){  // part of the CI
      steps {  // Run sh script
        sh "scripts/build.sh" // invoce the shell and run script
      }
    }
    stage('Run unit tests'){  // part of the CI
      steps {  // Run sh script
        sh "scripts/unit_test.sh" // invoce the shell and run script
      }
    }
    // stage('Run integration test'){}  // part of the CI

    stage('Deploy website'){  // part of the CI
      steps {  // Run sh script
        sh "scripts/deploy_website.sh" // invoce the shell and run script
      }
    }
  }
}

4.1.7. Add scripts

4.1.7.1. build.sh
echo "I am running: $0"

4.1.7.2. unit_tests.sh

echo "I am running: $0"

4.1.7.3. deploy_website.sh

echo "I am running: $0"

4.1.8. Git

4.1.8.1. Git initialize

$ git init

4.1.8.2. Add .gitignore

node_modules
jenkins_home
.terraform
.DS_Store
.idea/*

4.1.8.3. Create a new branch if not exist

git checkout -b master // production ready branch

4.1.8.4. Update the index using the current content found in the working tree, to prepare the content staged for the next commit.

git add .

4.1.8.5. Commit files

git commit -m "Initialize repository."

4.1.9. Jenkins

docker-compose up jenkins

4.1.9.1. Login http://localhost:8080/

username: admin password: in /var/jenkins_home/secrets/initialAdminPassword

4.1.9.2. Start building your software project

Create job
Enter an item name: "explore_california_ci"
Multibranch Pipeline - Ok
Configuration:
General:
Display Name: Explore California CI
Description: This pipeline deploys the Explore California website.
Branch Sources: Add Source: Git: Project Repository: file:///app (because the code was mounted directly in the container) or https://github.com/ILLYAKO/DevOps-foundations.git
Build Configuration
Mode by Jenkins
Script Path Jenkins
Save - Apply

4.1.9.3. Go to Dashboard -> Explore California CI -> Scan Multibranch Pipeline Now

⚠️ **GitHub.com Fallback** ⚠️