Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Ruby microservices with Docker - Sergii Koba

504 views

Published on

Ruby Meditation #21
April 14, 2018
Kharkiv

Published in: Technology
  • Be the first to comment

  • Be the first to like this

Ruby microservices with Docker - Sergii Koba

  1. 1. Ruby microservices With Docker
  2. 2. About me ● Serhii Koba ● Position ○ Web Team lead at MobiDev ○ Lecturer at KhAI ● Fullstack ○ BE: Ruby, Go, Php, Python ○ FE: Vue JS ○ DO: Docker, Ansible ○ IoT, Blockchain, Android ● Blogger a bit ● Personal moto: Until we learn - we live Social media: ● http://1devblog.org ● github: @sergey-koba-mobidev ● twitter: @KobaSerhii ● facebook: @kobaserhii
  3. 3. What we will talk about ● How Docker helps building Ruby Microservices ● Automating development processes ● Repeatable environment ● Optimization of Docker images ● Refactoring Docker Compose files ● Central logging with Docker ● Routing requests to microservices ● Deploy?
  4. 4. What we will not talk about ● When to use microservices ● What is the best framework/language for microservices ● How to migrate to microservices ● How to install Docker, Docker Compose ● Docker for Dummies :) ● Is Rails good for Microservices
  5. 5. Docker
  6. 6. Two words about Docker ● Application build and deploy tool ● Containers based virtualization (shares host OS kernel) ● Each container is based on image ● Each piece of software (service) should be a container ● Lightweight ● Secure https://www.docker.com/what-container
  7. 7. Ruby microservice: Sinatra + Trailblazer # service.rb # https://github.com/bark-iot/ require 'sinatra' set :bind, '0.0.0.0' set :port, 80 get '/houses' do result = House::List.(user_id: USER['id']) if result.success? body House::Representer.for_collection.new(result['models'] ).to_json else status 422 body result['contract.default'].errors.messages.uniq.to_json end end # concepts/house/operations/list.rb class House < Sequel::Model(DB) class List < Trailblazer::Operation step Contract::Build() step Contract::Validate() step :list_by_user_id #... def list_by_user_id(options, params:, **) options['models'] = House.where(user_id: params[:user_id]).all options['models'] end end end Hi @apotonick
  8. 8. Dockerfile FROM ruby:2.5.0 # Pg RUN apt-get update -qq && apt-get install -y build-essential libpq-dev lsb-release ENV APP_ROOT /app RUN mkdir $APP_ROOT WORKDIR $APP_ROOT EXPOSE 80 # Bundle COPY Gemfile /app/Gemfile COPY Gemfile.lock /app/Gemfile.lock RUN gem update bundler && bundle install --jobs 4 # Copy the rest of source COPY . /app Build image docker build -f Dockerfile.test -t house-service/test . List images docker images REPOSITORY | TAG | IMAGE ID | CREATED | SIZE house-service/test | latest | c9b0fe5b01d1 | 2 minutes ago | 985MB Run docker run -i -p 80:80 house-service/test bundle exec ruby service.rb 985 MB
  9. 9. Dockerfile: Use Alpine linux FROM ruby:2.5.0-alpine # Pg RUN apk --update --upgrade add postgresql-dev git build-base ENV APP_ROOT /app RUN mkdir $APP_ROOT WORKDIR $APP_ROOT EXPOSE 80 # Bundle COPY Gemfile /app/Gemfile COPY Gemfile.lock /app/Gemfile.lock RUN gem update bundler && bundle install --jobs 4 # Copy the rest of source COPY . /app Build image docker build -f Dockerfile.test -t house-service/test . List images docker images REPOSITORY | TAG | IMAGE ID | CREATED | SIZE house-service/test | latest | ab58dd76e0d3 | 2 minutes ago | 320MB 320 MB
  10. 10. Dockerfile: Multistage builds FROM ruby:2.5.0-alpine as bundler # CODE FROM PREVIOUS SLIDE # Stage 2 FROM ruby:2.5.0-alpine RUN apk --update --upgrade add postgresql-dev EXPOSE 80 ENV APP_ROOT /app RUN mkdir $APP_ROOT WORKDIR $APP_ROOT # Copy the rest of source COPY . /app COPY --from=bundler /usr/local/bundle /usr/local/bundle Build image docker build -f Dockerfile.test -t house-service/test . List images docker images REPOSITORY | TAG | IMAGE ID | CREATED | SIZE house-service/test | latest | 8f5a46611b7e | 2 minutes ago | 127MB 127 MB
  11. 11. Dockerfile: Results 8x
  12. 12. Docker Compose
  13. 13. Docker Compose: Yml file Let's assume each service is placed in a separate folder ./house-service ./user-service ./device-service ... house-service: build: ../house-service command: bundle exec ruby service.rb networks: - my-network ports: - 80 volumes: - ../house-service/:/app environment: POSTGRES_USER: my_user POSTGRES_PASSWORD: my_pass POSTGRES_DB: my_db RACK_ENV: development stdin_open: true tty: true
  14. 14. Docker Compose: Multiple services services: db: image: postgres:latest ports: - "5432:5432" environment: POSTGRES_USER: my_user POSTGRES_PASSWORD: my_pass POSTGRES_DB: my_db redis: image: redis:4.0.5 house-service: … user-service: … user-service: build: ../user-service command: bundle exec ruby service.rb networks: - my-network ports: - 80 volumes: - ../user-service/:/app environment: POSTGRES_USER: my_user POSTGRES_PASSWORD: my_pass POSTGRES_DB: my_db RACK_ENV: development stdin_open: true tty: true Run services: docker-compose up -d 16 lines
  15. 15. Docker Compose: extension fields ● Yaml anchors and extend ● Docker Compose file version 3.4 ● “X-” sections (extension fields) x-base-service: &base-service command: bundle exec ruby service.rb networks: - my-network ports: - 80 environment: POSTGRES_USER: my_user POSTGRES_PASSWORD: my_pass POSTGRES_DB: my_db stdin_open: true tty: true users-service: << : *base-service build: ../users-service volumes: - ../users-service/:/app 5 lines
  16. 16. Docker Compose: Production In production we want to do things differently: ● Use images instead of build ● Remove stdin and tty ● Remove volumes with app code ● Remove test containers x-base-service: &base-service command: bundle exec ruby service.rb networks: - my-network ports: - 80 environment: POSTGRES_USER: my_user POSTGRES_PASSWORD: my_pass POSTGRES_DB: my_db RACK_ENV: production users-service: << : *base-service image: myapp/users-service:latest
  17. 17. Two options: ● Two separate docker-compose.yml files ● Overriding files Docker Compose: Multiple Files Run Development docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d # docker-compose.yml PRODUCTION house-service: image: myapp/house-service:latest command: bundle exec ruby service.rb networks: - my-network ports: - 80 environment: POSTGRES_USER: my_user POSTGRES_PASSWORD: my_pass POSTGRES_DB: my_db # docker-compose.dev.yml DEV house-service: build: ../user-service command: bundle exec ruby service.rb environment: RACK_ENV: development
  18. 18. Central Logging
  19. 19. Logging ● View logs from multiple Docker containers ● Tracing data across multiple containers ● Tracing user action across multiple containers ● Aggregate logs ● Search logs View logs on a single container docker-compose -f docker-compose.yml -f docker-compose.dev.yml logs house-service
  20. 20. Logging: ELK stack
  21. 21. Logging: ELK with Docker logstash: build: docker/logstash/ command: logstash -f /etc/logstash/conf.d/logstash.conf ports: - "12201:12201/udp" kibana: build: docker/kibana/ ports: - "5601:5601" elasticsearch: image: elasticsearch:latest command: elasticsearch -Enetwork.host=0.0.0.0 ports: - "9200:9200" - "9300:9300" environment: ES_JAVA_OPTS: "-Xms750m -Xmx750m" volumes: - /usr/share/elasticsearch/data
  22. 22. Logging: ELK with Docker # kibana.yml port: 5601 host: "0.0.0.0" elasticsearch_url: "http://elasticsearch:9200" elasticsearch_preserve_host: true kibana_index: ".kibana" default_app_id: "discover" request_timeout: 300000 shard_timeout: 0 verify_ssl: true bundled_plugin_ids: - plugins/dashboard/index - ... // logstash.conf input { gelf {} } output { elasticsearch { hosts => "elasticsearch:9200" } }
  23. 23. Logging: Gelf driver for Docker containers x-logging: &logging driver: gelf options: gelf-address: 'udp://localhost:12201' tag: '{{.Name}}' house-service: << : *logging user-service: << : *logging Template variable
  24. 24. Logging: Add log messages to Ruby service class House < Sequel::Model(DB) class Create < Trailblazer::Operation step Model(House, :new) step :generate_key_and_secret step Contract::Persist() step :log_success failure :log_failure #... def log_success(options, params:, model:, **) LOGGER.info "[#{self.class}] Created house with params #{params.to_json}. House: #{House::Representer.new(model).to_json}" end def log_failure(options, params:, **) LOGGER.info "[#{self.class}] Failed to create house with params #{params.to_json}" end end
  25. 25. Logging: Kibana
  26. 26. Routing
  27. 27. Routing: first try house-service: ... ports: - 3000 user-service: ... ports: - 3001 device-service: ... ports: - 3002 # house-service/service.rb require 'sinatra' set :bind, '0.0.0.0' set :port, 3000 # user-service/service.rb require 'sinatra' set :bind, '0.0.0.0' set :port, 3001 http://localhost:3000 http://localhost:3001
  28. 28. Routing: Docker haproxy lb: image: dockercloud/haproxy links: - house-service volumes: - /var/run/docker.sock:/var/run/docker.sock ports: - 80:80 house-service: ... environment: VIRTUAL_HOST: "*/houses*" VIRTUAL_HOST_WEIGHT: 102 http://samos-it.com/posts/docker-multi-website-single-ip-host-haproxy.html Docker Swarm Compatible
  29. 29. Possible routers - Docker cloud proxy (Deprecated) - Traefik https://traefik.io/ - Caddy https://caddyserver.com/ - Zookeeper
  30. 30. Conclusion
  31. 31. Conclusion ● All in one environment and tools for Ruby microservices ● Minimized Docker images ● Minimized Docker Compose file ● Central logging and tracing using ELK ● Routing (balancing) requests What’s next? ● Authorization layers? ● gRPC for inter communication? ● Message broker? ● Migrating monolith Rails app to Microservices? :)

×