How to continous integration using ansible

@negativespace

Story

I often write a provisioning tool for a team. But sometimes some coworker break the provision codes. The coworker is not wrong. It's my fault not to do continuous integration.

source code

https://github.com/okamuuu/circleci-docker-provisioning

prepare

Let's create a sample project.

mkdir circleci-docker-provisioning && cd $_
mkdir -p keys provisioning/inventory provisioning/roles/nodejs/tasks
touch .gitignore Makefile Dockerfile circle.yml 
touch provisioning/playbook.yml provisioning/inventory/docker provisioning/roles/nodejs/tasks/main.yml keys/.gitkeep

Save the public key and secret key in keys/* dir. It contains .gitkeep. it means not to add keys to this repository. Let’s create .gitignore

keys/*
!.gitkeep

commit it.

git init && git add . && git commit -m "initial commit"

create a public key and secret key

Let's create keys without the password. The following commands will generate keys/docker_id_rsa and keys/docker_id_rsa.pub.

ssh-keygen -P "" -f keys/docker_id_rsa

Dockerfile

Usually, it is not necessary to include SSHD in docker container, but I want to do provision by SSHD in this case.

Let's create Dockerfile

FROM ubuntu:16.04

RUN apt-get update && apt-get install -y sudo openssh-server python
RUN mkdir -p /var/run/sshd

RUN useradd -m -d /home/docker -s /bin/bash docker
RUN echo "docker:docker" | chpasswd
RUN mkdir /home/docker/.ssh
RUN chmod 700 /home/docker/.ssh
COPY ./keys/docker_id_rsa.pub /home/docker/.ssh/authorized_keys
RUN chmod 600 /home/docker/.ssh/authorized_keys
RUN chown -R docker:docker /home/docker/.ssh

RUN echo "docker ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers

EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]

Build it

docker build -t provisioning_sshd .

Run it

docker run -d -p 40122:22 --name test_sshd provisioning_sshd

Check it out.

ssh docker@localhost -p 40122 -i keys/docker_id_rsa 

Ansible

Let's install Node.js.

create provisioning/inventory/docker

docker ansible_user=docker ansible_port=40122 ansible_host=localhost ansible_ssh_private_key_file=./keys/docker_id_rsa

create provisioning/roles/nodejs/tasks/main.yml

---
- name: install Node.js
  become: yes 
  apt: name={{ item }} state=installed
  with_items:
    - nodejs
    - npm

- name: nmp cache clean
  command: npm cache clean

- name: install n command
  become: yes 
  command: npm install n -g

- name: install node v6.10.0
  become: yes 
  command: n 6.10.0

Let's create provisioning/playbook.yml

---
- hosts: docker
  become: true
  roles:
    - nodejs

start provision.

ansible-playbook ./provisioning/playbook.yml -i ./provisioning/inventory/docker

check it out.

ssh docker@localhost -p 40122 -i keys/docker_id_rsa 'node -v'
v6.10.0

The following command is straightforward code to test. If the status code is 0, it's okay.

test `ssh docker@localhost -p 40122 -i keys/docker_id_rsa 'node -v'` = "v6.10.0"
echo $? 

Makefile

Let's Makefile.

docker.keygen:
    ssh-keygen -P "" -f keys/docker_id_rsa

docker.build:
    docker build -t provisioning_sshd .

docker.start:
    docker run -d -p 40122:22 --name test_sshd provisioning_sshd

docker.stop:
    ssh-keygen -R "[localhost]:40122"
    docker rm -f `docker ps -aq`

docker.provision:
    ansible-playbook ./provisioning/playbook.yml -i ./provisioning/inventory/docker

docker.provision.ci:
    ansible-playbook ./provisioning/playbook.yml -i ./provisioning/inventory/docker --private-key=./keys/docker_id_rsa

docker.test:
    test `ssh docker@localhost -p 40122 -i keys/docker_id_rsa 'node -v'` = "v6.10.0"
    @echo "It looks like optimal"

CircleCI

I will create Docker container on host OS in CircleCI. And host OS needs to use Python and Ansible.

Let's create circle.yml.

machine:
  python:
    version: 2.7.10
  services:
    - docker

dependencies:
  pre:
    - pip install ansible
  override:
    - docker info
    - make docker.keygen
    - docker build -t provisioning_sshd .

test:
  override:
    - docker run -d -p 40122:22 provisioning_sshd
    - make docker.provision.ci
    - make docker.test

recap

It was my idea of how to provision my ansible code. My goal was just to check the provision success to run to end in this case.

There are many ways to do continuous integration. We can choose the better way in depends.

enjoy:)