├── .gitignore ├── .rspec ├── .semaphore ├── deploy-k8s.yml ├── docker-build.yml └── semaphore.yml ├── Dockerfile ├── Dockerfile.ci ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── app.rb ├── config.ru ├── deployment.yml ├── pipeline.png └── spec ├── app_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | .bundle 3 | vendor/bundle 4 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | -------------------------------------------------------------------------------- /.semaphore/deploy-k8s.yml: -------------------------------------------------------------------------------- 1 | version: v1.0 2 | name: Deploy to Kubernetes 3 | agent: 4 | machine: 5 | type: e1-standard-2 6 | os_image: ubuntu2004 7 | blocks: 8 | - name: Deploy to Kubernetes 9 | task: 10 | secrets: 11 | - name: do-k8s 12 | - name: dockerhub 13 | env_vars: [] 14 | jobs: 15 | - name: Deploy 16 | commands: 17 | - checkout 18 | - kubectl get nodes 19 | - kubectl get pods 20 | - envsubst < deployment.yml | tee _deploy.yml 21 | - kubectl apply -f _deploy.yml 22 | - name: Tag latest release 23 | task: 24 | secrets: 25 | - name: dockerhub 26 | jobs: 27 | - name: docker tag latest 28 | commands: 29 | - 'echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin' 30 | - 'docker pull "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:$SEMAPHORE_WORKFLOW_ID' 31 | - 'docker tag "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:$SEMAPHORE_WORKFLOW_ID "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:latest' 32 | - 'docker push "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:latest' 33 | -------------------------------------------------------------------------------- /.semaphore/docker-build.yml: -------------------------------------------------------------------------------- 1 | version: v1.0 2 | name: Docker build 3 | agent: 4 | machine: 5 | type: e1-standard-2 6 | os_image: ubuntu2004 7 | blocks: 8 | - name: Build 9 | task: 10 | secrets: 11 | - name: dockerhub 12 | jobs: 13 | - name: Docker build 14 | commands: 15 | - 'echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin' 16 | - checkout 17 | - 'docker pull "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:latest || true' 18 | - 'docker build --cache-from "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:latest -t "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:$SEMAPHORE_WORKFLOW_ID -f Dockerfile.ci .' 19 | - docker images 20 | - 'docker push "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:$SEMAPHORE_WORKFLOW_ID' 21 | promotions: 22 | - name: Deploy to Kubernetes 23 | pipeline_file: deploy-k8s.yml 24 | -------------------------------------------------------------------------------- /.semaphore/semaphore.yml: -------------------------------------------------------------------------------- 1 | version: v1.0 2 | name: CI 3 | agent: 4 | machine: 5 | type: e1-standard-2 6 | os_image: ubuntu2004 7 | blocks: 8 | - name: Install dependencies 9 | task: 10 | jobs: 11 | - name: bundle install 12 | commands: 13 | - checkout 14 | - cache restore 15 | - bundle config set deployment 'true' 16 | - bundle install --path vendor/bundle 17 | - cache store 18 | - name: Tests 19 | task: 20 | jobs: 21 | - name: rspec 22 | commands: 23 | - checkout 24 | - cache restore 25 | - bundle install --path vendor/bundle 26 | - bundle exec rspec 27 | promotions: 28 | - name: Dockerize 29 | pipeline_file: docker-build.yml 30 | auto_promote_on: 31 | - result: passed 32 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:3.1 2 | 3 | RUN apt-get update -qq && apt-get install -y build-essential 4 | 5 | ENV APP_HOME /app 6 | RUN mkdir $APP_HOME 7 | WORKDIR $APP_HOME 8 | 9 | ADD Gemfile* $APP_HOME/ 10 | RUN bundle config set deployment 'true' 11 | RUN bundle install --path vendor/bundle 12 | 13 | ADD . $APP_HOME 14 | 15 | EXPOSE 4567 16 | 17 | CMD ["bundle", "exec", "rackup", "--host", "0.0.0.0", "-p", "4567"] 18 | -------------------------------------------------------------------------------- /Dockerfile.ci: -------------------------------------------------------------------------------- 1 | #Pull image from Semaphore Container Registry 2 | FROM registry.semaphoreci.com/ruby:2.7 3 | 4 | RUN apt-get update -qq && apt-get install -y build-essential 5 | 6 | ENV APP_HOME /app 7 | RUN mkdir $APP_HOME 8 | WORKDIR $APP_HOME 9 | 10 | ADD Gemfile* $APP_HOME/ 11 | RUN bundle install --without development test 12 | 13 | ADD . $APP_HOME 14 | 15 | EXPOSE 4567 16 | 17 | CMD ["bundle", "exec", "rackup", "--host", "0.0.0.0", "-p", "4567"] 18 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "sinatra" 4 | gem "puma" 5 | 6 | group :test do 7 | gem "rack-test" 8 | gem "rspec" 9 | end 10 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | diff-lcs (1.5.0) 5 | mustermann (1.1.1) 6 | ruby2_keywords (~> 0.0.1) 7 | nio4r (2.5.8) 8 | puma (5.6.4) 9 | nio4r (~> 2.0) 10 | rack (2.2.3) 11 | rack-protection (2.2.0) 12 | rack 13 | rack-test (1.1.0) 14 | rack (>= 1.0, < 3) 15 | rspec (3.11.0) 16 | rspec-core (~> 3.11.0) 17 | rspec-expectations (~> 3.11.0) 18 | rspec-mocks (~> 3.11.0) 19 | rspec-core (3.11.0) 20 | rspec-support (~> 3.11.0) 21 | rspec-expectations (3.11.0) 22 | diff-lcs (>= 1.2.0, < 2.0) 23 | rspec-support (~> 3.11.0) 24 | rspec-mocks (3.11.1) 25 | diff-lcs (>= 1.2.0, < 2.0) 26 | rspec-support (~> 3.11.0) 27 | rspec-support (3.11.0) 28 | ruby2_keywords (0.0.5) 29 | sinatra (2.2.0) 30 | mustermann (~> 1.0) 31 | rack (~> 2.2) 32 | rack-protection (= 2.2.0) 33 | tilt (~> 2.0) 34 | tilt (2.0.10) 35 | 36 | PLATFORMS 37 | ruby 38 | 39 | DEPENDENCIES 40 | puma 41 | rack-test 42 | rspec 43 | sinatra 44 | 45 | BUNDLED WITH 46 | 2.3.13 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Rendered Text 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Semaphore CI/CD demo for Kubernetes 2 | 3 | [![Build Status](https://semaphore-demos.semaphoreci.com/badges/semaphore-demo-ruby-kubernetes-eins/branches/master.svg)](https://semaphore-demos.semaphoreci.com/projects/semaphore-demo-ruby-kubernetes-eins) 4 | 5 | This is an example application and CI/CD pipeline showing how to build, test and 6 | deploy a microservice to Kubernetes using Semaphore 2.0. 7 | 8 | Ingredients: 9 | 10 | - Ruby Sinatra as web framework 11 | - RSpec for tests 12 | - Packaged in a Docker container 13 | - Container pushed to Docker Hub registry 14 | - Deployed to Kubernetes 15 | 16 | ## CI/CD on Semaphore 17 | 18 | If you're new to Semaphore, feel free to fork this repository and use it to 19 | [create a project](https://docs.semaphoreci.com/article/63-your-first-project). 20 | 21 | The CI/CD pipeline is defined in `.semaphore` directory and looks like this: 22 | 23 | ![CI/CD pipeline on Semaphore](pipeline.png) 24 | 25 | ## Local application setup 26 | 27 | To run the microservice: 28 | 29 | ``` 30 | bundle install --path vendor/bundle 31 | bundle exec rackup 32 | ``` 33 | 34 | To run tests: 35 | 36 | ``` 37 | bundle exec rspec 38 | ``` 39 | 40 | To build and run Docker container: 41 | 42 | ``` 43 | docker build -t semaphore-demo-ruby-kubernetes . 44 | docker run -p 80:4567 semaphore-demo-ruby-kubernetes 45 | curl localhost 46 | > hello world :)) 47 | ``` 48 | 49 | ## Additional documentation 50 | 51 | - [CI/CD for Microservices on DigitalOcean Kubernetes](https://semaphoreci.com/blog/cicd-microservices-digitalocean-kubernetes) 52 | - [CI/CD for Microservices on Kubernetes](https://docs.semaphoreci.com/examples/ci-cd-for-microservices-on-kubernetes) 53 | 54 | ## License 55 | 56 | Copyright (c) 2022 Rendered Text 57 | 58 | Distributed under the MIT License. See the file LICENSE. 59 | -------------------------------------------------------------------------------- /app.rb: -------------------------------------------------------------------------------- 1 | require "sinatra" 2 | 3 | class App < Sinatra::Base 4 | get "/" do 5 | "hello world :))" 6 | end 7 | 8 | get "/says" do 9 | "Put this in your pipe & smoke it!" 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | $LOAD_PATH << File.join(File.dirname(__FILE__)) 2 | 3 | require "app" 4 | require "logger" 5 | 6 | use Rack::CommonLogger, Logger.new(STDOUT) 7 | run App 8 | -------------------------------------------------------------------------------- /deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: semaphore-demo-ruby-kubernetes 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: semaphore-demo-ruby-kubernetes 10 | template: 11 | metadata: 12 | labels: 13 | app: semaphore-demo-ruby-kubernetes 14 | spec: 15 | imagePullSecrets: 16 | - name: dockerhub 17 | containers: 18 | - name: semaphore-demo-ruby-kubernetes 19 | image: $DOCKER_USERNAME/semaphore-demo-ruby-kubernetes:$SEMAPHORE_WORKFLOW_ID 20 | 21 | --- 22 | 23 | apiVersion: v1 24 | kind: Service 25 | metadata: 26 | name: semaphore-demo-ruby-kubernetes-lb 27 | spec: 28 | selector: 29 | app: semaphore-demo-ruby-kubernetes 30 | type: LoadBalancer 31 | ports: 32 | - port: 80 33 | targetPort: 4567 34 | -------------------------------------------------------------------------------- /pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/semaphoreci-demos/semaphore-demo-ruby-kubernetes/eb655dbe751e5a1ba5d2cf1ed32f88d18bc6c172/pipeline.png -------------------------------------------------------------------------------- /spec/app_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path '../spec_helper.rb', __FILE__ 2 | 3 | RSpec.describe "My Sinatra Application" do 4 | it "has a home" do 5 | get "/" 6 | expect(last_response).to be_ok 7 | expect(last_response.body).to include("hello") 8 | end 9 | 10 | it "says something funny" do 11 | get "/says" 12 | expect(last_response.body).to include("smoke it") 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rack/test' 2 | require 'rspec' 3 | 4 | ENV['RACK_ENV'] = 'test' 5 | 6 | require File.expand_path '../../app.rb', __FILE__ 7 | 8 | module RSpecMixin 9 | include Rack::Test::Methods 10 | def app() App end 11 | end 12 | 13 | RSpec.configure { |c| c.include RSpecMixin } 14 | --------------------------------------------------------------------------------