3 September 2015

Installing Weblogic with Chef


In my previous blog I discussed using Chef to deploy Weblogic SOA suite, in this blog I will show you how to create a simple Weblogic Cluster on a virtual machine with two managed servers using Chef.  This solution uses a Chef Cookbook named weblogic which contains recipes and templates, an environment and roles to model the infrastructure as code.  Two recipes have been created, one to install the Weblogic binaries ‘install-wls.rb and the second ‘create-domain.rb’ which creates the Weblogic Domain with an Admin Server and two managed servers in a Cluster.  The recipes read attributes defined in the environment ‘weblogic_dev’

The Weblogic jar installer was downloaded from Oracle and stored in a Nexus repository.  The source files for the Cookbooks, Environment and Roles were created in the local Chef repository on the Chef Workstation in the following folder structure, and then uploaded to the Chef Server.


The source for the recipes, templates and environment is described below:

Recipes


install-wls.rb

This recipe installs the Weblogic binaries by:

Downloading the Weblogic installer jar file from a Nexus repository (2)
Creates the user oracle, group oinstaller and weblogic home directory (3)
Creates the Oracle Inventory and installer response files from the templates ora_inventory.rsp.erb and wls-12c.rsp.erb (3).  The templates have placeholders which are substituted with attributes read from the Node object.
Executes the Weblogic jar file in silent mode referencing the two response files created by running :

java -jar weblogic-12.1.3.jar -silent  -reponseFile responsefile -InvPtrLoc OraInventoryFile
The source for the recipe is listed below:

# (1) Get the attributes from the Node object on the server that the recipe is run on (Defined in environment)
os_user = node['weblogic']['os_user']
os_installer_group = node['weblogic']['os_installer_group']
user_home = File.join("/home", os_user)
nexus_url = node['weblogic']['nexus_url']
repository = node['weblogic']['repository']
group_id = node['weblogic']['group_id']
artifact_id = node['weblogic']['artifact_id']
version = node['weblogic']['version']
packaging = node['weblogic']['packaging']

# (2) Create the user/group used to install Weblogic and the WLS home directory
group os_installer_group do
action :create
append true
end

user os_user do
supports :manage_home => true
comment "Oracle user"
gid os_installer_group
home user_home
shell "/bin/bash"
end

# Create FMW Directory
directory node['weblogic']['oracle_home'] do
owner os_user
group os_installer_group
recursive true
action :create
end

# (3) Download the Weblogic installer from Nexus
installer_jar = File.join(user_home, "#{artifact_id}-#{version}.#{packaging}")
remote_file "download Oracle Weblogic Server" do
#source "#{nexus_url}?r=#{repository}&g=#{group_id}&a=#{artifact_id}&v=#{version}&p=#{packaging}"
source "file:///mnt/hgfs/vmwareData/Alan/SOA/fmw_12.1.3.0.0_wls.jar"
path installer_jar
owner os_user
group os_installer_group
end

# (4) Create OraInventory and Installer response files to allow silent install
ora_inventory_directory = File.join(user_home, "oraInventory")
ora_inventory_file = File.join( ora_inventory_directory, "ora_inventory.rsp")

directory ora_inventory_directory do
owner os_user
group os_installer_group
recursive true
action :create
end

template ora_inventory_file do
source "ora_inventory.rsp.erb"
owner os_user
group os_installer_group
variables(
ora_inventory_directory: ora_inventory_directory,
install_group: os_installer_group
)
owner os_user
group os_installer_group
end

# Create Response File
response_file = File.join(user_home, "wls-12c.rsp")
oracle_home = node['weblogic']['oracle_home']

template response_file do
source "wls-12c.rsp.erb"
variables(
oracle_home: oracle_home
)
owner os_user
group os_installer_group
end

# (5) Install Weblogic Server by executing the jar command defining the appropriate command line options to install silently
install_command = "#{node['weblogic']['java_home']}/bin/java -jar #{installer_jar} -silent -responseFile #{response_file} -invPtrLoc #{ora_inventory_file}"

execute install_command do
cwd user_home
user os_user
group os_installer_group
action :run
creates "#{oracle_home}/oraInst.loc"
end
 create-domain.rb

The main purpose of this recipe is to create a WLST script from the template create_domain.py.erb that configures the Weblogic domain offline.  The template defines the following WLST helper functions to create the respective Weblogic components to define the cluster:

createManagedServer(servername,  machinename, address, port)
createAdminServer(servername, address, port)
createMachine(machinename, address, port)
createCluster(clustername, address, port)
assignCluster(clustername, server)

The main function createCustomDomain calls the above functions to create and configure the domain using attributes defined in the environment as json objects.  A Weblogic domain requires one Admin server and can have multiple clusters, which each can contain one or more managed servers.  The managed servers can be located on one machine or across multiple machines, with each machine requiring a Node manager to be configured.  The configuration for the clusters, machines and managed servers is defined in a json object array in the environment.  The respective json object array for the Clusters, machines and managed servers is passed into the template and the appropriate block of code iterates through each item in the array to generate a call to helper function with the correct values passed as arguments.  The code snippet below, with some code moved for clarity, shows how the call to the createMachine helper function is generated by iterating through the items in the machines object array.
def createCustomDomain():
print 'Creating Domain... ' + domain;
readTemplate('<%= @wl_home %>/common/templates/wls/wls.jar', domain_mode)

setOption('ServerStartMode', start_mode)
. . . .

<% @machines.each do |machine| -%>
createMachine('<%= machine['name'] %>', '<%= machine['nm_address'] %>',
<%= machine['nm_port'] %>)
<% end -%>

. . . .
writeDomain(domain_path)
closeTemplate()
The source for the recipe is listed below:
# (1) Get the attributes from the Node object of the server recipe is run on # (Defined in environment)
os_user = node['weblogic']['os_user']
os_installer_group = node['weblogic']['os_installer_group']
middleware_home = node['weblogic']['oracle_home']
weblogic_home = "#{middleware_home}/wlserver"
common_home = "#{middleware_home}/oracle_common"
domains_path = File.join(middleware_home, "domains")
domain_name = node['wls_domain']['name']
domain_py = File.join(middleware_home, "create_domain.py")

# (2) Create the WLS Dommains directory
directory domains_path do
owner os_user
group os_installer_group
recursive true
action :create
end

# (3) Create the WLST script to create the domain, passing in variables read from # the node's attribute hash map. Save the script to the server for execution
template domain_py do
source "create_domain.py.erb"
variables(
domain_mode: node['wls_domain']['mode'],
domains_path: domains_path,
domain: domain_name,
start_mode: node['wls_domain']['start_mode'],
crossdomain_enabled: node['wls_domain']['crossdomain_enabled'],
username: node['wls_domain']['admin_username'],
password: node['wls_domain']['admin_password'],
wl_home: weblogic_home,
machines: node['wls_domain']['machines'],
admin_server: node['wls_domain']['admin_server'],
managed_servers: node['wls_domain']['managed_servers'],
clusters: node['wls_domain']['clusters']
)
owner os_user
group os_installer_group
end

# (4) Run the WLST script to create the domain offline
ENV['ORACLE_HOME'] = middleware_home

execute "#{weblogic_home}/common/bin/wlst.sh #{domain_py}" do
environment "CONFIG_JVM_ARGS" => "-Djava.security.egd=file:/dev/./urandom"
user os_user
group os_installer_group
action :run
creates "#{domains_path}/#{domain_name}/config/config.xml"
end

Templates


The recipe install-wls uses two templates, ora_inventory.rsp.erb and wls-12c.rsp.erb to create the response file used for the silent install and the file oraInst.loc which specifies the location of the Oracle Inventory directory. The template create_domain.py.erb is called by the recipe create_domain and defines the WLST script which is run to create the domain. The recipe passes in a json object array for the Clusters, Machines and Managed servers to the template, a code block is defined which iterates through each object array creating a call to the helper functions to create a call to the methods createMachine, createManagedServer and createMachine for each item in the respective array.

create_domain.py.erb
domain_mode='<%= @domain_mode %>'
domain_path='<%= @domains_path %>/<%= @domain %>'
domain='<%= @domain %>'
start_mode='<%= @start_mode %>'
crossdomain_enabled=<%= @crossdomain_enabled %>
admin_username='<%= @username %>'
admin_password='<%= @password %>'

def createManagedServer(servername, machinename, address, port):
print 'Creating Managed Server Configuration... ' + servername;
cd("/")
create(servername, "Server")
cd("/Servers/" + servername)

if machinename:
set('Machine', machinename)

set('ListenAddress', address)
set('ListenPort', int(port))

def createAdminServer(servername, address, port):
print 'Creating Admin Server Configuration... ' + servername;
cd("/")

cd("/Servers/" + servername)
set('ListenAddress', address)
set('ListenPort', int(port))
cd('/')
cd('Security/base_domain/User/weblogic')
set('Name',admin_username)
cmo.setPassword(admin_password)

def createMachine(machinename, address, port):
print 'Creating Machine Configuration... ' + machinename;

try:
cd('/')
create(machinename, 'Machine')
except BeanAlreadyExistsException:
print 'Machine ' + machinename + ' already exists';

cd('Machine/' + machinename)
create(machinename, 'NodeManager')
cd('NodeManager/' + machinename)
set('ListenAddress', address)
set('ListenPort', int(port))

def createCluster(clustername, address, port):
print 'Creating Cluster Configuration... ' + clustername;
cd('/')
create(clustername, 'Cluster')
cd('Clusters/' + clustername)
set('MulticastAddress', address)
set('MulticastPort', port)
set('WeblogicPluginEnabled', 'true')

def assignCluster(clustername, server):
print 'Assigning server ' + server + ' to Cluster ' + clustername;
cd('/')
assign('Server', server, 'Cluster', clustername)

def createCustomDomain():
print 'Creating Domain... ' + domain;
readTemplate('<%= @wl_home %>/common/templates/wls/wls.jar', domain_mode)

setOption('ServerStartMode', start_mode)

createAdminServer('<%= @admin_server['name'] %>',
'<%= @admin_server['address'] %>', <%= @admin_server['port'] %>)

<% @clusters.each do |cluster| -%>
createCluster('<%= cluster['name'] %>', '<%= cluster['multicast_address'] %>',
<%= cluster['multicast_port'] %>)
<% end -%>

<% @machines.each do |machine| -%>
createMachine('<%= machine['name'] %>', '<%= machine['nm_address'] %>',
<%= machine['nm_port'] %>)
<% end -%>

<% @managed_servers.each do |managed_server| -%>
createManagedServer('<%= managed_server['name'] %>',
'<%= managed_server['machine_name'] %>',
'<%= managed_server['address'] %>',
<%= managed_server['port'] %>)

assignCluster('<%= managed_server['cluster_name'] %>',
'<%= managed_server['name'] %>')
<% end -%>

writeDomain(domain_path)
closeTemplate()

createCustomDomain()
dumpStack()
print('Exiting...')
exit()

Environment


The environment defines all the attributes referenced by the recipes.

weblogic_dev.json

{
"name": "weblogic_dev",
"description": "",
"cookbook_versions": {},
"json_class": "Chef::Environment",
"chef_type": "environment",
"default_attributes": {
"weblogic": {
"nexus_url": "http://chefserver01.c2b2.co.uk:8081/nexus/service/local/artifact/maven/redirect",
"repository": "C2B2",
"group_id": "com.oracle",
"artifact_id": "weblogic",
"version": "12.1.3",
"packaging": "jar",
"os_user": "oracle",
"os_installer_group": "orainstall",
"wls_version": "12.1.3",
"oracle_home": "/home/oracle/c2b2/middleware/product/fmw",
"java_home": "/opt/jdk1.8.0_25",
"installer_jar": "/home/oracle/fmw_12.1.3.0.0_wls.jar"

},
"wls_domain": {
"name": "c2b2-domain",
"mode": "Compact",
"start_mode": "dev",
"crossdomain_enabled": "true",
"admin_username": "weblogic",
"admin_password": "welcome1",
"admin_server": {
"name": "AdminServer",
"machine_name": "wls1",
"address": "wls1.c2b2.co.uk",
"port": "7001"

},
"managed_servers": [
{ "name": "node1", "machine_name": "wls1", "address": "wls1.c2b2.co.uk",
"port": "8001", "cluster_name": "c2b2-cluster"},
{ "name": "node2", "machine_name": "wls1", "address": "wls1.c2b2.co.uk",
"port": "9001", "cluster_name": "c2b2-cluster"}
],
"machines": [
{ "name": "wls1", "nm_address": "wls1.c2b2.co.uk", "nm_port": "5556"}
],
"clusters": [
{"name": "c2b2-cluster", "multicast_address": "237.0.0.101",
"multicast_port": "9200"}
]
}
},
"override_attributes": {}
}
The source files are uploaded using the knife tool using the following commands:
knife cookbook upload weblogic
knife environment from file /ahs1/chef-repo/weblogic/environments/weblogic_dev.json
knife role from file /ahs1/chef-repo/weblogic/roles/weblogic_domain.json
knife role from file /ahs1/chef-repo/weblogic/roles/weblogic_install.json
The virtual machine hostname wls1.c2b2.co.uk is bootstrapped which installs the chef-client and registers as a Node with the Chef server.  Using the console, the Node was edited and the environment weblogic_dev is assigned to it and the recipes weblogic_install and weblogic_domain added to the Nodes run list.


To install Weblogic and configure the domain, the chef-client is run on the node wls1.c2b2.co.uk by executing the following knife command:

knife ssh -x afryer “chef_environment:weblogic_dev” “sudo -u root chef-client -l info”

The knife ssh command queries the Chef Server returning a list of matching nodes in weblogic_dev environment, in this case wls1.c2b2.co.uk. An ssh session is started on this node as the user ‘afryer’ and runs the Chef client with sudo access. The Chef client connects to the Chef server and updates the attributes in the node’s hash map and executes the recipes defined in the weblogic_install and weblogic_domain Role’s run-list. The recipes read the attributes from the nodes hash map (defined in the environments) and performs the operations in the recipes, installing and configuring a Weblogic domain on the node.

Hopefully this blog has given you an insight into how you can automate a Weblogic installation using Chef. Extending the techniques used above should give you a good basis for a reusable and extensible set of scripts to make your installations quicker and easier.



5 comments :

  1. Hi Alan, really good post.

    We are starting doing some work with Chef and Weblogic, but we don't understand how can we use Chef to deploy a weblogic cluster across different machines. Is it possible to use your cookbook for that?

    Thanks,
    Jorge

    ReplyDelete
    Replies
    1. Look at https://github.com/chef/chef-provisioning . It will accomplish what you want to do in a fairly lightweight fashion.

      Delete
  2. Thanks Alan, Really useful!

    ReplyDelete
  3. May I know how do we implement in chef-solo mode? We don't have any chef-server.

    ReplyDelete