an example group is a class in which the block passed to
describe is evaluated
The blocks passed to it are evaluated in the
context of an instance of that class
nested groups using the describe or context
methods
can declare example groups using either describe or context
can declare examples within a group using any of it, specify, or
example
Declare a shared example group using shared_examples, and then include it
in any group using include_examples.
Nearly anything that can be declared within an example group can be declared
within a shared example group.
shared_context and include_context.
When a class is passed to describe, you can access it from an example
using the described_class method
rspec-core stores a metadata hash with every example and group
Example groups are defined by a describe or
context block, which is eagerly evaluated when the spec file is
loaded
Examples -- typically defined by an it block -- and any other
blocks with per-example semantics -- such as a before(:example) hook -- are
evaluated in the context of
an instance of the example group class to which the example belongs.
Examples are not executed when the spec file is loaded
run any examples until all spec files have been loaded
can overwrite the default label by passing it to the input method
configure the html
of any of them
disable labels, hints or error
add a hint,
an error, or even a placeholder
add an inline label
pass any html attribute straight to the input, by using the :input_html
option
use the :defaults option in simple_form_fo
Simple Form generates a wrapper div around your label and input by default, you can pass
any html attribute to that wrapper as well using the :wrapper_html option,
By default all inputs are required
the required property of any input can be overwritten
Simple Form will look at the column type in the database and use an
appropriate input for the column
lets you overwrite the default input type it creates
can also render boolean
attributes using as: :select to show a dropdown.
give the :disabled option to Simple Form, and it'll automatically mark
the wrapper as disabled with a CSS class
Simple Form accepts same options as their corresponding input type helper in Rails
Any extra option passed to these methods will be rendered as html option.
use label, hint, input_field, error and full_error helpers
to strip away all the div wrappers around the <input> field
is to use f.input_field
changing boolean_style from default value :nested to :inline
overriding the :collection option
Collections can be arrays or ranges, and when a :collection is given the :select input will be
rendered by default
Other types of collection
are :radio_buttons and :check_boxes
label_method
value_method
Both of these options also accept
lambda/procs
define a to_label method on your model as Simple Form will search for
and use :to_label as a :label_method first if it is found
create grouped collection selects, that will use the html optgroup tags
Grouped collection inputs accept the same :label_method and :value_method options
group_method
group_label_method
configured with a default value to be used on the site through the
SimpleForm.country_priority and SimpleForm.time_zone_priority helpers.
association
association
render a :select input for choosing the :company, and another
:select input with :multiple option for the :roles
all options available to :select,
:radio_buttons and :check_boxes are also available to association
declare different labels and values
the association helper is currently only tested with Active Record
f.input
f.select
create a <button> element
simple_fields_for
Creates a collection of radio inputs with labels associated
Creates a collection of checkboxes with labels associated
collection_radio_buttons
collection_check_boxes
associations in your model
Role.all
the html element you will get for each attribute
according to its database definition
redefine existing Simple Form inputs by creating a new class with the same name
Simple Form uses all power of I18n API to lookup labels, hints, prompts and placeholders
specify defaults for all
models under the 'defaults' key
Simple Form will always look for a default attribute translation under the "defaults" key if no
specific is found inside the model key
Simple Form will fallback to default human_attribute_name from Rails when no other
translation is found for labels.
Simple Form will only do the lookup for options if you give a collection
composed of symbols only.
"Add %{model}"
translations for labels, hints and placeholders for a namespaced model, e.g.
Admin::User, should be placed under admin_user, not under admin/user
This difference exists because Simple Form relies on object_name provided by Rails'
FormBuilder to determine the translation path for a given object instead of i18n_key from the
object itself.
configure how your components will be rendered using the wrappers API
optional
unless_blank
By default, Simple Form will generate input field types and attributes that are supported in HTML5
The HTML5
extensions include the new field types such as email, number, search, url, tel, and the new
attributes such as required, autofocus, maxlength, min, max, step.
If you want to have all other HTML 5 features, such as the new field types, you can disable only
the browser validation
add novalidate to a specific form by setting the option on the form itself
the utility used a simple recursive descent parser without backtracking, which gave unary operators precedence over binary operators and ignored trailing arguments.
The x-hack is effective because no unary operators can start with x.
the x-hack could be used to work around certain bugs all the way up until 2015, seven years after StackOverflow wrote it off as an archaic relic of the past!
The Dash issue of [ "(" = ")" ] was originally reported in a form that affected both Bash 3.2.48 and Dash 0.5.4 in 2008. You can still see this on macOS bash today
The generate attribute is used to inform Terragrunt to generate the Terraform code for configuring the backend.
The find_in_parent_folders() helper will automatically search up the directory tree to find the root terragrunt.hcl and inherit the remote_state configuration from it.
Unlike the backend configurations, provider configurations support variables,
if you needed to modify the configuration to expose another parameter (e.g
session_name), you would have to then go through each of your modules to make this change.
instructs Terragrunt to create the file provider.tf in the working directory (where Terragrunt calls terraform)
before it calls any of the Terraform commands
large modules should be considered harmful.
it is a Bad Idea to define all of your environments (dev, stage, prod, etc), or even a large amount of infrastructure (servers, databases, load balancers, DNS, etc), in a single Terraform module.
Large modules are slow, insecure, hard to update, hard to code review, hard to test, and brittle (i.e., you have all your eggs in one basket).
Terragrunt allows you to define your Terraform code once and to promote a versioned, immutable “artifact” of that exact same code from environment to environment.
Continuous integration gives you confidence in your code. To extend that confidence to the release process, your deployment operations need to come with a safety belt.
these Kubernetes objects ensure that you can progressively deploy, roll back and scale your applications without downtime.
A pod is just a group of containers (it can be a group of one container) that run on the same machine, and share a few things together.
the containers within a pod can communicate with each other over localhost
From a network perspective, all the processes in these containers are local.
we can never create a standalone container: the closest we can do is create a pod, with a single container in it.
Kubernetes is a declarative system (by opposition to imperative systems).
All we can do, is describe what we want to have, and wait for Kubernetes to take action to reconcile what we have, with what we want to have.
In other words, we can say, “I would like a 40-feet long blue container with yellow doors“, and Kubernetes will find such a container for us. If it doesn’t exist, it will build it; if there is already one but it’s green with red doors, it will paint it for us; if there is already a container of the right size and color, Kubernetes will do nothing, since what we have already matches what we want.
The specification of a replica set looks very much like the specification of a pod, except that it carries a number, indicating how many replicas
What happens if we change that definition? Suddenly, there are zero pods matching the new specification.
the creation of new pods could happen in a more gradual manner.
the specification for a deployment looks very much like the one for a replica set: it features a pod specification, and a number of replicas.
Deployments, however, don’t create or delete pods directly.
When we update a deployment and adjust the number of replicas, it passes that update down to the replica set.
When we update the pod specification, the deployment creates a new replica set with the updated pod specification. That replica set has an initial size of zero. Then, the size of that replica set is progressively increased, while decreasing the size of the other replica set.
we are going to fade in (turn up the volume) on the new replica set, while we fade out (turn down the volume) on the old one.
During the whole process, requests are sent to pods of both the old and new replica sets, without any downtime for our users.
A readiness probe is a test that we add to a container specification.
Kubernetes supports three ways of implementing readiness probes:Running a command inside a container;Making an HTTP(S) request against a container; orOpening a TCP socket against a container.
When we roll out a new version, Kubernetes will wait for the new pod to mark itself as “ready” before moving on to the next one.
If there is no readiness probe, then the container is considered as ready, as long as it could be started.
MaxSurge indicates how many extra pods we are willing to run during a rolling update, while MaxUnavailable indicates how many pods we can lose during the rolling update.
Setting MaxUnavailable to 0 means, “do not shutdown any old pod before a new one is up and ready to serve traffic“.
Setting MaxSurge to 100% means, “immediately start all the new pods“, implying that we have enough spare capacity on our cluster, and that we want to go as fast as possible.
kubectl rollout undo deployment web
the replica set doesn’t look at the pods’ specifications, but only at their labels.
A replica set contains a selector, which is a logical expression that “selects” (just like a SELECT query in SQL) a number of pods.
it is absolutely possible to manually create pods with these labels, but running a different image (or with different settings), and fool our replica set.
Selectors are also used by services, which act as the load balancers for Kubernetes traffic, internal and external.
internal IP address (denoted by the name ClusterIP)
during a rollout, the deployment doesn’t reconfigure or inform the load balancer that pods are started and stopped. It happens automatically through the selector of the service associated to the load balancer.
a pod is added as a valid endpoint for a service only if all its containers pass their readiness check. In other words, a pod starts receiving traffic only once it’s actually ready for it.
In blue/green deployment, we want to instantly switch over all the traffic from the old version to the new, instead of doing it progressively
We can achieve blue/green deployment by creating multiple deployments (in the Kubernetes sense), and then switching from one to another by changing the selector of our service
"Continuous integration gives you confidence in your code. To extend that confidence to the release process, your deployment operations need to come with a safety belt."
In this case, Elasticsearch. And because Elasticsearch can be down or struggling, or the network can be down, the shipper would ideally be able to buffer and retry
Logstash is typically used for collecting, parsing, and storing logs for future use as part of log management.
Logstash’s biggest con or “Achille’s heel” has always been performance and resource consumption (the default heap size is 1GB).
This can be a problem for high traffic deployments, when Logstash servers would need to be comparable with the Elasticsearch ones.
Filebeat was made to be that lightweight log shipper that pushes to Logstash or Elasticsearch.
differences between Logstash and Filebeat are that Logstash has more functionality, while Filebeat takes less resources.
Filebeat is just a tiny binary with no dependencies.
For example, how aggressive it should be in searching for new files to tail and when to close file handles when a file didn’t get changes for a while.
For example, the apache module will point Filebeat to default access.log and error.log paths
Filebeat’s scope is very limited,
Initially it could only send logs to Logstash and Elasticsearch, but now it can send to Kafka and Redis, and in 5.x it also gains filtering capabilities.
Filebeat can parse JSON
you can push directly from Filebeat to Elasticsearch, and have Elasticsearch do both parsing and storing.
You shouldn’t need a buffer when tailing files because, just as Logstash, Filebeat remembers where it left off
For larger deployments, you’d typically use Kafka as a queue instead, because Filebeat can talk to Kafka as well
The default syslog daemon on most Linux distros, rsyslog can do so much more than just picking logs from the syslog socket and writing to /var/log/messages.
It can tail files, parse them, buffer (on disk and in memory) and ship to a number of destinations, including Elasticsearch.
rsyslog is the fastest shipper
Its grammar-based parsing module (mmnormalize) works at constant speed no matter the number of rules (we tested this claim).
use it as a simple router/shipper, any decent machine will be limited by network bandwidth
It’s also one of the lightest parsers you can find, depending on the configured memory buffers.
rsyslog requires more work to get the configuration right
the main difference between Logstash and rsyslog is that Logstash is easier to use while rsyslog lighter.
rsyslog fits well in scenarios where you either need something very light yet capable (an appliance, a small VM, collecting syslog from within a Docker container).
rsyslog also works well when you need that ultimate performance.
syslog-ng as an alternative to rsyslog (though historically it was actually the other way around).
a modular syslog daemon, that can do much more than just syslog
Unlike rsyslog, it features a clear, consistent configuration format and has nice documentation.
Similarly to rsyslog, you’d probably want to deploy syslog-ng on boxes where resources are tight, yet you do want to perform potentially complex processing.
syslog-ng has an easier, more polished feel than rsyslog, but likely not that ultimate performance
Fluentd was built on the idea of logging in JSON wherever possible (which is a practice we totally agree with) so that log shippers down the line don’t have to guess which substring is which field of which type.
Fluentd plugins are in Ruby and very easy to write.
structured data through Fluentd, it’s not made to have the flexibility of other shippers on this list (Filebeat excluded).
Fluent Bit, which is to Fluentd similar to how Filebeat is for Logstash.
Fluentd is a good fit when you have diverse or exotic sources and destinations for your logs, because of the number of plugins.
Splunk isn’t a log shipper, it’s a commercial logging solution
Graylog is another complete logging solution, an open-source alternative to Splunk.
everything goes through graylog-server, from authentication to queries.
Graylog is nice because you have a complete logging solution, but it’s going to be harder to customize than an ELK stack.
SysBench is not a tool which you can use to tune configurations of your MySQL servers (unless you prepared LUA scripts with custom workload or your workload happen to be very similar to the benchmark workloads that SysBench comes with)
it is great for is to compare performance of different hardware.
Every new server acquired should go through a warm-up period during which you will stress it to pinpoint potential hardware defects
by executing OLTP workload which overloads the server, or you can also use dedicated benchmarks for CPU, disk and memory.
bulk_insert.lua. This test can be used to benchmark the ability of MySQL to perform multi-row inserts.
All oltp_* scripts share a common table structure. First two of them (oltp_delete.lua and oltp_insert.lua) execute single DELETE and INSERT statements.
oltp_point_select, oltp_update_index and oltp_update_non_index. These will execute a subset of queries - primary key-based selects, index-based updates and non-index-based updates.
you can run different workload patterns using the same benchmark.
Warmup helps to identify “regular” throughput by executing benchmark for a predefined time, allowing to warm up the cache, buffer pools etc.
By default SysBench will attempt to execute queries as fast as possible. To simulate slower traffic this option may be used. You can define here how many transactions should be executed per second.
SysBench gives you ability to generate different types of data distribution.
decide if SysBench should use prepared statements (as long as they are available in the given datastore - for MySQL it means PS will be enabled by default) or not.
sysbench ./sysbench/src/lua/oltp_read_write.lua help
By default, SysBench will attempt to execute queries in explicit transaction. This way the dataset will stay consistent and not affected: SysBench will, for example, execute INSERT and DELETE on the same row, making sure the data set will not grow (impacting your ability to reproduce results).
specify error codes from MySQL which SysBench should ignore (and not kill the connection).
the two most popular benchmarks - OLTP read only and OLTP read/write.
1 million rows will result in ~240 MB of data. Ten tables, 1000 000 rows each equals to 2.4GB
by default, SysBench looks for ‘sbtest’ schema which has to exist before you prepare the data set. You may have to create it manually.
pass ‘--histogram’ argument to SysBench
~48GB of data (20 tables, 10 000 000 rows each).
if you don’t understand why the performance was like it was, you may draw incorrect conclusions out of the benchmarks.
Researching issues felt like bouncing a rubber ball between teams, hard to catch the root cause and harder yet to stop from bouncing between one another.
In the past, Edge Engineering had ops-focused teams and SRE specialists who owned the deploy+operate+support parts of the software life cycle
devs could push code themselves when needed, and also were responsible for off-hours production issues and support requests
What were we trying to accomplish and why weren’t we being successful?
These specialized roles create efficiencies within each segment while potentially creating inefficiencies across the entire life cycle.
Grouping differing specialists together into one team can reduce silos, but having different people do each role adds communication overhead, introduces bottlenecks, and inhibits the effectiveness of feedback loops.
devops principles
develops a system also be responsible for operating and supporting that system
Each development team owns deployment issues, performance bugs, capacity planning, alerting gaps, partner support, and so on.
Those centralized teams act as force multipliers by turning their specialized knowledge into reusable building blocks.
Communication and alignment are the keys to success.
Full cycle developers are expected to be knowledgeable and effective in all areas of the software life cycle.
ramping up on areas they haven’t focused on before
We run dev bootcamps and other forms of ongoing training to impart this knowledge and build up these skills
“how can I automate what is needed to operate this system?”
“what self-service tool will enable my partners to answer their questions without needing me to be involved?”
A full cycle developer thinks and acts like an SWE, SDET, and SRE. At times they create software that solves business problems, at other times they write test cases for that, and still other times they automate operational aspects of that system.
the need for continuous delivery pipelines, monitoring/observability, and so on.
Tooling and automation help to scale expertise, but no tool will solve every problem in the developer productivity and operations space
Keyfiles are bare-minimum forms of security and are best suited for testing or
development environments.
With keyfile authentication, each
mongod instances in the replica set uses the contents of the keyfile as the
shared password for authenticating other members in the deployment.
On UNIX systems, the keyfile must not have group or world
permissions.
The templates/ directory is for template files. When Helm evaluates a chart,
it will send all of the files in the templates/ directory through the template
rendering engine. It then collects the results of those templates and sends them
on to Kubernetes.
The charts/ directory may contain other charts
(which we call subcharts).
we
recommend using the suffix .yaml for YAML files and .tpl for helpers.
The helm get manifest command takes a release name (full-coral) and prints
out all of the Kubernetes resources that were uploaded to the server.
Each file
begins with --- to indicate the start of a YAML document, and then is followed
by an automatically generated comment line that tells us what template file
generated this YAML document.
name: field is limited to 63 characters because of limitations to
the DNS system.
The template directive {{ .Release.Name }} injects the release name into the
template. The values that are passed into a template can be thought of as
namespaced objects, where a dot (.) separates each namespaced element.
The leading dot before Release indicates that we start with the top-most
namespace for this scope
helm install --debug --dry-run goodly-guppy ./mychart. This will render the templates. But instead
of installing the chart, it will return the rendered template to you
Using --dry-run will make it easier to test your code, but it won't ensure
that Kubernetes itself will accept the templates you generate.
It's best not to
assume that your chart will install just because --dry-run works.
But if you maintain a CHANGELOG in this format, and/or your Git tags are also your Docker tags, you can get the previous version and use cache the this image version.
« Docker layer caching » is enough to optimize the build time.
Cache in CI/CD is about saving directories or files across pipelines.
We're building a Docker image, dependencies are installed inside a container.We can't cache a dependencies directory if it doesn't exists in the job workspace.
Dependencies will always be installed from a container but will be extracted by the GitLab Runner in the job workspace. Our goal is to send the cached version in the build context.
We set the directories to cache in the job settings with a key to share the cache per branch and stage.
To avoid old dependencies to be mixed with the new ones, at the risk of keeping unused dependencies in cache, which would make cache and images heavier.
If you need to cache directories in testing jobs, it's easier: use volumes !
version your cache keys !
sharing Docker image between jobs
In every job, we automatically get artifacts from previous stages.
docker save $DOCKER_CI_IMAGE | gzip > app.tar.gz
I personally use the « push / pull » technique,
we docker push after the build, then we docker pull if needed in the next jobs.
musl is an implementation of C standard library. It is more lightweight, faster and simpler than glibc used by other Linux distros, such as Ubuntu.
Some of it stems from how musl (and therefore also Alpine) handles DNS (it's always DNS), more specifically, musl (by design) doesn't support DNS-over-TCP.
By using Alpine, you're getting "free" chaos engineering for you cluster.
this DNS issue does not manifest in Docker container. It can only happen in Kubernetes, so if you test locally, everything will work fine, and you will only find out about unfixable issue when you deploy the application to a cluster.
if your application requires CGO_ENABLED=1, you will obviously run into issue with Alpine.
Microservices also bring a set of additional benefits, such as easier scaling, the possibility to use multiple programming languages and technologies, and others.
Java is a frequent choice for building a microservices architecture as it is a mature language tested over decades and has a multitude of microservices-favorable frameworks, such as legendary Spring, Jersey, Play, and others.
A monolithic architecture keeps it all simple. An app has just one server and one database.
All the connections between units are inside-code calls.
split our application into microservices and got a set of units completely independent for deployment and maintenance.
Each of microservices responsible for a certain business function communicates either via sync HTTP/REST or async AMQP protocols.
ensure seamless communication between newly created distributed components.
The gateway became an entry point for all clients’ requests.
We also set the Zuul 2 framework for our gateway service so that the application could leverage the benefits of non-blocking HTTP calls.
we've implemented the Eureka server as our server discovery that keeps a list of utilized user profile and order servers to help them discover each other.
We also have a message broker (RabbitMQ) as an intermediary between the notification server and the rest of the servers to allow async messaging in-between.
microservices can definitely help when it comes to creating complex applications that deal with huge loads and need continuous improvement and scaling.
When you use specific docker image, make sure you have the Dependency Proxy enabled so the image doesn’t have to be downloaded again for every job.
stages are used to group items that can run at the same time.
Instead of waiting for all jobs to finish, you can mark jobs as interruptible which signals a job to cancel when a new pipeline starts for the same branch