Skip to content

Commit 39a7e60

Browse files
authored
F/ansible post (#56)
* Added Ansible post
1 parent 67f1d34 commit 39a7e60

File tree

7 files changed

+222
-44
lines changed

7 files changed

+222
-44
lines changed
Lines changed: 39 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,46 @@
11
---
2-
- name: Install Java
3-
include_role:
4-
name: geerlingguy.java
5-
java_packages:
6-
- java-1.8.0-openjdk
2+
- name: Install Java
3+
include_role:
4+
name: geerlingguy.java
5+
java_packages:
6+
- java-1.8.0-openjdk
77

8-
- name: Including Supervisord
9-
include_role:
10-
name: supervisord
8+
- name: Including Supervisord
9+
include_role:
10+
name: supervisord
1111

12-
- name: "Creating {{app_user_group}} Group"
13-
group:
14-
name: "{{app_user_group}}"
15-
state: present
12+
- name: "Creating {{app_user_group}} Group"
13+
group:
14+
name: "{{app_user_group}}"
15+
state: present
1616

17-
- name: "Creating {{app_user}} User"
18-
user:
19-
name: "{{app_user}}"
20-
group: "{{app_user_group}}"
17+
- name: "Creating {{app_user}} User"
18+
user:
19+
name: "{{app_user}}"
20+
group: "{{app_user_group}}"
2121

22-
- name: "Creating {{app_directory}} Directory"
23-
file:
24-
path: "{{app_directory}}"
25-
state: directory
26-
mode: 0755
27-
owner: "{{app_user}}"
28-
group: "{{app_user_group}}"
22+
- name: "Creating {{app_directory}} Directory"
23+
file:
24+
path: "{{app_directory}}"
25+
state: directory
26+
mode: 0755
27+
owner: "{{app_user}}"
28+
group: "{{app_user_group}}"
2929

30-
- name: "Copying supervisor {{app_name}}.conf"
31-
template:
32-
src: supervisorapp.conf.j2
33-
dest: "/etc/supervisor.d/{{app_name}}.conf"
34-
notify: restart supervisord
30+
- name: "Copying supervisor {{app_name}}.conf"
31+
template:
32+
src: supervisorapp.conf.j2
33+
dest: "/etc/supervisor.d/{{app_name}}.conf"
34+
notify: restart supervisord
3535

36-
# If we start refreshing configs in the app we can
37-
# turn off the restart. For now we load configs once
38-
# at boot. So restart the app any time it changes.
39-
- name: Copying stubbornjava secure.conf
40-
template:
41-
src: secure.conf.j2
42-
dest: "{{app_directory}}/secure.conf"
43-
owner: "{{app_user}}"
44-
group: "{{app_user_group}}"
45-
mode: u=r,g=r,o=
46-
notify: restart supervisord
36+
# If we start refreshing configs in the app we can
37+
# turn off the restart. For now we load configs once
38+
# at boot. So restart the app any time it changes.
39+
- name: Copying stubbornjava secure.conf
40+
template:
41+
src: secure.conf.j2
42+
dest: "{{app_directory}}/secure.conf"
43+
owner: "{{app_user}}"
44+
group: "{{app_user_group}}"
45+
mode: u=r,g=r,o=
46+
notify: restart supervisord

ansible/roles/supervisord/handlers/main.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,3 @@
44

55
- name: "supervisorctl restart {{app_name}}"
66
command: "supervisorctl restart {{app_name}}"
7-
# TODO: Don't always restart supervisord. We can reload configs
8-
# or even just the app when we deploy new code. Restarting it all is overkill.

ansible/stubbornjava.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
become: true
55
roles:
66
- role: common
7-
become: true
87
- role: apps/jvm_app_base
98
app_name: stubbornjava
109
app_command: "java8 -Denv={{env}} -Xmx640m -cp 'stubbornjava-all.jar' com.stubbornjava.webapp.StubbornJavaWebApp"
11-
become: true

stubbornjava-common/src/main/java/com/stubbornjava/common/undertow/handlers/CustomHandlers.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public static HttpHandler gzip(HttpHandler next) {
5353
new GzipEncodingProvider(), 1000,
5454
// Anything under a content-length of 20 will not be gzipped
5555
Predicates.truePredicate()
56-
//Predicates.parse("max-content-size(20)") // https://issues.jboss.org/browse/UNDERTOW-1234
56+
//Predicates.maxContentSize(20) // https://issues.jboss.org/browse/UNDERTOW-1234
5757
))
5858
.setNext(next);
5959
}

stubbornjava-webapp/src/main/java/com/stubbornjava/webapp/post/PostData.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,42 @@ public class PostData {
688688
))
689689
.build()
690690
);
691+
posts.add(PostRaw.builder()
692+
.postId(1L)
693+
.title("Installing Java, supervisord and other service dependencies with Ansible")
694+
.metaDesc("Example using Ansible to install java8, supervisord, users, groups and file structures in a repeatable way. Configuring AWS EC2 instances with Ansible example.")
695+
.dateCreated(LocalDateTime.parse("2017-11-17T01:15:30"))
696+
.dateUpdated(LocalDateTime.parse("2017-11-17T01:15:30"))
697+
.javaLibs(Lists.newArrayList())
698+
.tags(Lists.newArrayList(Tags.Ansible, Tags.Supervisord))
699+
.gitFileReferences(Lists.newArrayList(
700+
FileReference.stubbornJava(
701+
"serverPlaybook",
702+
"ansible/stubbornjava.yml")
703+
, FileReference.stubbornJava(
704+
"hosts",
705+
"ansible/inventories/production/hosts")
706+
, FileReference.stubbornJava(
707+
"appBaseTasks",
708+
"ansible/roles/apps/jvm_app_base/tasks/main.yml")
709+
, FileReference.stubbornJava(
710+
"appBaseVars",
711+
"ansible/roles/apps/jvm_app_base/vars/main.yml")
712+
, FileReference.stubbornJava(
713+
"appBaseTemplatesSupervisord",
714+
"ansible/roles/apps/jvm_app_base/templates/supervisorapp.conf.j2")
715+
, FileReference.stubbornJava(
716+
"appBaseTemplatesConf",
717+
"ansible/roles/apps/jvm_app_base/templates/secure.conf.j2")
718+
, FileReference.stubbornJava(
719+
"supervisord",
720+
"ansible/roles/supervisord/tasks/main.yml")
721+
, FileReference.stubbornJava(
722+
"supervisordHandlers",
723+
"ansible/roles/supervisord/handlers/main.yml")
724+
))
725+
.build()
726+
);
691727
}
692728

693729
public static List<PostRaw> getPosts() {

stubbornjava-webapp/src/main/java/com/stubbornjava/webapp/post/Tags.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ public class Tags {
2828
public static final Tag Docker = addTag(new Tag(922789907642700121L, "Docker"));
2929
public static final Tag MySQL = addTag(new Tag(922794262771082027L, "MySQL"));
3030
public static final Tag Elasticsearch = addTag(new Tag(922794262770139008L, "Elasticsearch"));
31+
public static final Tag Ansible = addTag(new Tag(922794262770139008L, "Ansible"));
32+
public static final Tag Supervisord = addTag(new Tag(922794262770139008L, "Supervisord"));
3133

3234
private static Tag addTag(Tag tag) {
3335
TAGS.add(tag);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
<div class="anchored-md">
2+
{{#assign "md1"}}
3+
Let's say you spent the past 18 months building your MVP application complete with several microservices, SQL, NoSQL, Kafka, Redis, Elasticsearch, service discovery, and on demand configuration updates. You are the bleeding edge of technology and are prepared to scale to millions of users overnight. Your only problem? everything is running locally and nothing has been set up in AWS. Go hire a dev ops engineer and delay your launch another month or two, good luck.
4+
5+
Let's try that again and rewind 17 months. You decided to go with a single Java monolith app which you build a single [deployable jar with Gradle using the shadowjar plugin](/posts/multi-project-builds-with-gradle-and-fat-jars-with-shadow). Sure, monoliths aren't sexy but they work. Realistically speaking they can scale pretty far, remember when Twitter was still on monoliths with millions of tweets a day? Our goal is to get the app up and running in as little effort as possible but keeping in mind we may want to be able to scale out to more than one server. No we don't need auto scaling just yet! and probably won't for a long while if ever.
6+
7+
Head over to [Amazon Web Services](https://aws.amazon.com/) and spin up a single server. Maybe even a `t2.micro` will be good enough for your use case. It can always be resized later. Next it would be nice to have a way to reliably install all of the dependencies for our app. Enter [Ansible](https://www.ansible.com/), Ansible is a SSH based automation solution for provisioning, configuring, and deploying applications using YAML. Take some time for a brief introduction [getting started with ansible](http://docs.ansible.com/ansible/latest/intro_getting_started.html) so we can dive right in. We will be following the second directory structure from [Ansible best practices](http://docs.ansible.com/ansible/latest/playbooks_best_practices.html).
8+
9+
## Ansible Playbook to configure our EC2 instance with Java and Supervisord
10+
An Ansible Playbook is often the main entry point to some task / process / configuration. It can contain which hosts specific commands are to be run on and include all of the reusable modules (roles) for the task at hand. We can also provide variables or variable overrides here. Our playbook will include everything our app needs to be running on our newly spun up EC2 instance. This includes the current version of Java and Supervisord which we will use to run and make sure the processes gets restarted if it crashes.
11+
{{/assign}}
12+
{{md md1}}
13+
{{> templates/src/widgets/code/code-snippet file=serverPlaybook section=serverPlaybook.content}}
14+
15+
{{#assign "md2"}}
16+
### Ansible Hosts
17+
The hosts property allows us to specify which hosts we want to execute the roles / tasks in this section on. In this case we are choosing our server group `stubbornjava`.
18+
{{/assign}}
19+
{{md md2}}
20+
{{> templates/src/widgets/code/code-snippet file=hosts section=hosts.content}}
21+
22+
{{#assign "md3"}}
23+
### Ansible custom Java application base role
24+
The `common` role is pretty trivial and just sets a time zone and enables the EPEL repo so we will skip it. The next role `jvm_app_base` is where most of our configuration happens. We are passing two parameters into our `jvm_app_base` role. Our `app_name` which is just an identifier for our app that will be used throughout the role and `app_command` the actual command we want to run.
25+
26+
#### Java application base variables
27+
Our base role includes the following variables. As you can see we are reusing the `app_name` passed in from the playbook to define some additional variables.
28+
{{/assign}}
29+
{{md md3}}
30+
{{> templates/src/widgets/code/code-snippet file=appBaseVars section=appBaseVars.content}}
31+
32+
{{#assign "md4"}}
33+
#### Java application base role
34+
Roles are one of Ansible primary building blocks. A role can be collections of tasks, variables, templates, files at its core. Our role is fairly generic so we can use it for multiple different Java applications if we want. Like a Playbook roles can also include other roles. We are using a public open source role [geerlingguy.java](https://galaxy.ansible.com/geerlingguy/java/) to install Java for us. We are also including another custom role for Supervisord. Next we will use some built in Ansible modules to create a user and group for our app as well as the directory structure we want. Notice the use of variables cascading down and so far mostly being populated by just our `app_name`. Finally we wrap up with adding our Supervisord config file for this app. This file tells us where to put logs, what command to run and any other Supervisord specific settings we care about. Finally we are copying over our environment specific configuration with all of our secrets using Ansible Vault.
35+
{{/assign}}
36+
{{md md4}}
37+
{{> templates/src/widgets/code/code-snippet file=appBaseTasks section=appBaseTasks.content}}
38+
39+
{{#assign "md8"}}
40+
As you can see the our config file with secrets is very bare bones. Most of our production config can live in our normal code base. The only thing we want split out is our secrets. We want them stored more securely. Also, we want our configs that change the most frequently to just be part of the build / deploy. Since our secrets change much less frequently it's reasonable to split it out.
41+
{{/assign}}
42+
{{md md8}}
43+
{{> templates/src/widgets/code/code-snippet file=appBaseTemplatesConf section=appBaseTemplatesConf.content}}
44+
45+
46+
{{#assign "md5"}}
47+
#### Supervisord
48+
In the previous role we mentioned including the Supervisord role as well as setting up the app specific conf file. Here is a quick look at the role with some files excluded, you can view them all [here](https://github.com/StubbornJava/StubbornJava/tree/master/ansible/roles/supervisord). The tasks should be fairly straight forward.
49+
{{/assign}}
50+
{{md md5}}
51+
{{> templates/src/widgets/code/code-snippet file=supervisord section=supervisord.content}}
52+
{{#assign "md6"}}
53+
Next we have our Supervisord handlers. Ansible handlers are like tasks but with a very special property. They are triggered by `notify` and only run once each at the end of all tasks. Changing the database passwords, changing supervisor conf, changing JVM properties or any of our roles can trigger a handler with notify. Whether its triggered one or N times it will only run once after all of the tasks have completed. This is to prevent you from accidentally restarting your service N times when updating commands.
54+
{{/assign}}
55+
{{md md6}}
56+
{{> templates/src/widgets/code/code-snippet file=supervisordHandlers section=supervisordHandlers.content}}
57+
58+
{{#assign "md7"}}
59+
Lastly for Supervisord here is the app specific config file that was referenced above in the jvm app role. Our `app_command` variable all the way from the playbook is what is getting passed here as the executing command.
60+
{{/assign}}
61+
{{md md7}}
62+
{{> templates/src/widgets/code/code-snippet file=appBaseTemplatesSupervisord section=appBaseTemplatesSupervisord.content}}
63+
64+
65+
{{#assign "md9"}}
66+
### Running our Playbook
67+
All thats left is to run our playbook. By the end of the execution our server should be fully configured and only missing the actual JAR file to execute. We will cover that with a deploy script in another post. If we decide we need some redundancy or larger servers simply update the Ansible hosts file with the new hosts and run the playbook again and all the servers should then be configured. There are even dynamic inventories for cloud infrastructure like AWS where you can target all instances based on their tags. There are also options to stagger the commands across servers so not all of them are updating at once.
68+
{{/assign}}
69+
{{md md9}}
70+
71+
<pre class="line-numbers"><code class="language-bash">ansible-playbook --vault-password-file .vault_pw.txt -i inventories/production stubbornjava.yml
72+
73+
PLAY [stubbornjava] ************************************************************
74+
75+
TASK [setup] *******************************************************************
76+
ok: [54.84.142.75]
77+
78+
TASK [common : EPEL] ***********************************************************
79+
ok: [54.84.142.75]
80+
81+
TASK [common : New York Time Zone] *********************************************
82+
ok: [54.84.142.75]
83+
84+
TASK [geerlingguy.java : Include OS-specific variables.] ***********************
85+
ok: [54.84.142.75]
86+
87+
TASK [geerlingguy.java : Include OS-specific variables for Fedora.] ************
88+
skipping: [54.84.142.75]
89+
90+
TASK [geerlingguy.java : Include version-specific variables for Ubuntu.] *******
91+
skipping: [54.84.142.75]
92+
93+
TASK [geerlingguy.java : Define java_packages.] ********************************
94+
ok: [54.84.142.75]
95+
96+
TASK [geerlingguy.java : include] **********************************************
97+
included: /usr/local/etc/ansible/roles/geerlingguy.java/tasks/setup-RedHat.yml for 54.84.142.75
98+
99+
TASK [geerlingguy.java : Ensure Java is installed.] ****************************
100+
ok: [54.84.142.75] => (item=[u'java-1.7.0-openjdk'])
101+
102+
TASK [geerlingguy.java : include] **********************************************
103+
skipping: [54.84.142.75]
104+
105+
TASK [geerlingguy.java : include] **********************************************
106+
skipping: [54.84.142.75]
107+
108+
TASK [geerlingguy.java : Set JAVA_HOME if configured.] *************************
109+
skipping: [54.84.142.75]
110+
111+
TASK [supervisord : Supervisor init script] ************************************
112+
ok: [54.84.142.75]
113+
114+
TASK [supervisord : Supervisor conf] *******************************************
115+
ok: [54.84.142.75]
116+
117+
TASK [supervisord : Supervisor conf dir] ***************************************
118+
ok: [54.84.142.75]
119+
120+
TASK [supervisord : Install supervisord] ***************************************
121+
ok: [54.84.142.75]
122+
123+
TASK [apps/jvm_app_base : Creating stubbornjava Group] *************************
124+
ok: [54.84.142.75]
125+
126+
TASK [apps/jvm_app_base : Creating stubbornjava User] **************************
127+
ok: [54.84.142.75]
128+
129+
TASK [apps/jvm_app_base : Creating /apps/stubbornjava Directory] ***************
130+
ok: [54.84.142.75]
131+
132+
TASK [apps/jvm_app_base : Copying supervisor stubbornjava.conf] ****************
133+
changed: [54.84.142.75]
134+
135+
TASK [apps/jvm_app_base : Copying stubbornjava secure.conf] ********************
136+
ok: [54.84.142.75]
137+
138+
RUNNING HANDLER [supervisord : restart supervisord] ****************************
139+
changed: [54.84.142.75]
140+
141+
PLAY RECAP *********************************************************************
142+
54.84.142.75 : ok=17 changed=2 unreachable=0 failed=0</code></pre>
143+
144+
</div>

0 commit comments

Comments
 (0)