When setting up MongoDB service under Windows, I reckon it is best to use a dedicated Windows server that has lashings of memory, and fast disk throughput.
However, MongoDB is remarkably tolerant of the hardware it runs on, and is happy with what it can get. For development work it is often a good idea to run on slow hardware just so you can better-sense the queries and aggregations that are taking too much time.
You will want a server that everyone can get to, and you will want it secure, so the ‘red lines’ are there: you want it on a server and you want authentication.
MongoDB 4.0 comes with its own MSI installer that can do a pretty good job, but I got more than usually interested in the installation process when it upgraded from 3.6, and then said I had no databases. I tried to hurriedly fix what it had done and lost remote access! Basically, the MSI installer is great for a new install but not quite so good for upgrading an existing installation on a server.
If I am upgrading, I…
- Download the mongodb Windows x64 installer
- Do a minimal install, without installing it as a service
- Create the options I need or want in a YAML config file, but without authentication
- Use mongod to install a windows service if necessary, specifying the location of the config file
- Make sure the service is set to autostart
- Check that the service is running and that I can access existing data
- Check the log file for any issues
- Check the firewall rule exists and ensure that the firewall rule specifies the right port
- Add the vital users such as the user admin if they aren’t yet in place
- Test local and remote access logging in via the users’ credentials
- Alter the YAML config file, adding authentication
- Stop and restart the service
- Test local and remote access via the users, and check that authentication is required
- Ensure that vital processes are working by creating a database, collection and document, and then reading it
Mongo runs from the mongod executable. mongod uses command-line parameters to determine a wide range of start-up parameters. These are all described in great detail in the documentation and help text. To make things neater, it will use a YAML config file as well.
All the events, warnings and errors are logged, and this information is important. MongoDB can start either as a service or as an executable. By running as a service, you can get it to run automatically on start-up and run under the Windows account that you specify.
A Windows service is the place to run MongoDB because the server just needs to be on, you can quickly and easily stop and restart the service either locally or remotely, and you can arrange to start any dependent service first. It is also easier to back up the databases.
The final red line is that you want it automated. My brain just isn’t big enough to remember the minutiae of installing stuff, and time spent fiddling with installs is time away from doing jobs that earn revenue. I therefore script everything I am likely to need to repeat.
In Windows, automation means PowerShell.
The basics
We’ll kick off with some easy stuff. We’ll set some parameters:
<#The default MongoDB Community Edition location #> $mongoDirectory='C:\Program Files\MongoDB\Server\4.0\bin' #set this to the directory of the version you want to install. $MongoPort=27017 #TCP port 27017 is the default port used by MongoDB. #set this to the port you wish to use. Hackers will scan port 27017 # routinely to find unauthenticated servers. # #have an admin just for maintaining users so it can be automated safely $Mongohost='MongoServer' $UserAdmin='UserAdmin' $UserAdminPassword='pavement hope coulomb calve epicycle' #and create a god-like Admin who is a system administrator $Admin='Admin' $AdminPassword='magnify republic rouble anaglyph ghost’ # now where all your mongo database and log files are $MongoDataPath='c:\data' # the classic default path
Obviously, you need to fill these in with your parameters (no, they aren’t my real passwords!) Note that this is just to get started. We won’t save actual passwords in a production PowerShell script. These will be typed in if there is nothing associated with your account on this machine, and saved in your user area as a secure password.
Later on, we’ll want to configure role-based access control and encrypt communication, but we’d need another article to describe all that.
Anyway, we’ll just make sure that those data paths that you’ve defined actually exist.
#first check that we've got log and data paths: if not then create them @("$MongoDataPath\db", "$MongoDataPath\log") | foreach{ if (!(Test-Path -Path $_)) { New-Item -ItemType directory -Path $_ } }
Run the MSI install
Now, we can run the MSI for the version of MongoDB that you are installing and select just an ordinary installation without installing it as a Windows service.
Check the paths to the executable and so on to see what has changed from the settings you’ve filled in above. If anything is different, then change the paths.
If you are performing an upgrade from an existing MongoDB service, then you can alter an existing Windows service merely by changing the ‘pathname’ attribute of the service to point the path at the new version of mongod. The –config parameter to the pathname attribute of the service can stay the same, even though you may need to change the actual file as part of the upgrade.Then stop and restart the service and you may find that all is well.
Now we’ll create an alias in PowerShell so we can use the mongo and mongod executables more easily:
Set-Alias mongod "$mongoDirectory\mongod.exe" Set-Alias mongo "$mongoDirectory\mongo.exe"
We can always get a bit of confidence by trying out getting the very useful help text from each:
mongo -h # and mongod -h
These switches can later on provide hours of innocent pleasure, trying things out.
Managing a service
If you are upgrading MongoDB, you need to do little other than altering the pathname to the new version of the mongod executable and the path to the new config file in the –config parameter, stop the service and then restart. Otherwise you will need to install the service.
It is best to start up mongod and mongos instances using a configuration file. You’ll find yourself doing quite a lot of starting and stopping of the system while you modify it to introduce better security and so on, so it is worth getting this right.
The configuration file contains settings that are equivalent to the mongod and mongos command-line options. Using a configuration file makes managing mongod and mongos options easier, especially if you are likely to be doing several deployments. You can also add comments to the configuration file to explain the server’s settings. mongod and mongos seems to read these config file settings at startup as startup parameters.
Here is a typical simple config file from the MongoDB documentation:
systemLog: destination: file path: "/var/log/mongodb/mongod.log" logAppend: true storage: journal: enabled: true processManagement: fork: true net: bindIp: 127.0.0.1 port: 27017 setParameter: enableLocalhostAuthBypass: false
…if you specify the path to the config file. We can easily write out a YAML file from PowerShell.
Here is a sample file.
Initially, I set things up as a service without authentication (authorization: “disabled”), create the users, stop the system and then restart with authentication.
$content=@" # mongod.conf # for documentation of all options, see: # http://docs.mongodb.org/manual/reference/configuration-options/ systemLog: destination: file path: $MongoDataPath\log\mongod.log logAppend: true # Where and how to store data. storage: dbPath: $MongoDataPath\db journal: enabled: true #processManagement: # fork: true net: port: $MongoPort # bind_ip: 127.0.0.1 #to turn off remote access bindIpAll: true processManagement: windowsService: serviceName: "MongoDB" displayName: "MongoDB" description: "mongod service" security: authorization: "enabled" setParameter: enableLocalhostAuthBypass: false "@ [IO.File]::WriteAllLines("$mongoDirectory\mongod.cfg" , $content) <# to ensure UTF8 without BOM #>
You will notice that some parameters are commented out and that parameters are, unlike the command line, arranged in sections to keep things tidy. Some parameters are in subsections too. With YAML, the indent is significant and only spaces are allowed for indentation.
So if MongoDB isn’t already installed as a service, and you have created the config file, we install it.
<# is MongoDB already running as an instance? #> $InstalledInstance=Get-WMIObject win32_service -Filter "name = 'MongoDB'" if ($InstalledInstance -eq $null) { #it is not running at all <# run mongo as a service #> mongod --config "$mongoDirectory\mongod.cfg" ` --install <# install as a service #>` }
If it all goes well, then you’ll get this when you examine the $installedInstance object.
ExitCode : 0 Name : MongoDB ProcessId : 6036 StartMode : Auto State : Running Status : OK
If everything goes well, then you can save a copy of the config file in source control or your CMS and move straight on to authentication and remote access, but real life isn’t like that. If things go wrong, you will need to work on the config file. One thing you might need to check is whether the service is starting with the right ‘Path to executable’ that references the current config file. To do so, run the following command:
Get-WmiObject win32_service | ?{$_.Name -eq 'MongoDB'} | select Name, DisplayName, State, PathName
If so, then there is probably a problem with the config file. In order to get it to start again with the updated config file, you can stop and restart the service easily.
Every time it restarts, it reads in the parameters from the file. If it doesn’t install, it will tell you why in the log file. This is how you can get the last twenty lines of the log:
Get-Content -Path "$MongoDataPath\log\mongod.log" -tail 20
I may just have been unlucky, but I didn’t get much help from the logs as to what it was in the YAML config file that it didn’t like when I was experimenting. It likes things precisely right but tends to sulk rather than explain what it didn’t like.
For this reason, you might save time by reverting to a very simple YAML config file that works (such as a personalised version of the typical config file above) and gradually adding stuff to it, taken from the PowerShell-generated version, stopping and starting the service every time, until it sulks, thereby isolating what the problem is.
To stop the service :–
stop-service -Name "MongoDB"
Then one can fiddle with the config file and save it.
To start the service :–
start-service -Name "MongoDB"
I run this:
$InstalledInstance=Get-WMIObject win32_service -Filter "name = 'MongoDB'"
… before and afterwards to check, and to maintain the InstalledInstance object
Eventually, it will burst into life.
Setting up the Windows Server firewall
Now we can create a firewall rule to allow MongoDB to get remote access:
$success = @(); #have we a rule in place already? $existingFirewallRule = Get-NetFirewallRule -DisplayName "MongoDB" -ErrorAction SilentlyContinue -ErrorVariable success if ($success.Count -gt 0) { <# Cut a hole in the firewall for the designated port #> New-NetFirewallRule <#now allow it through the firewall #> ` -DisplayName "MongoDB" ` -Description "Allow Remote Connections" ` -Direction Inbound ` -Protocol TCP ` -LocalPort $MongoPort ` -Action Allow <# Set the service to run automatically on startup #> Set-Service -Name "MongoDB" -StartupType automatic } else { if (($existingFirewallRule | Get-NetFirewallPortFilter).LocalPort -ne $MongoPort) { set-NetFirewallRule -DisplayName "MongoDB" -LocalPort $MongoPort } }
Note that we can use this routine to change the port we use for access, as well as to create the rule in the first place.
Managing the basic users for authentication
Next, if not already done so we create our user admin and the DBA with god-like powers. At this point, you will probably want to add all your users.
The user administrator login is just used for maintaining users and has no other godlike powers. In our case, we then want to create an admin role with wide powers. A user can be a person, a scheduled process, or a client application.
At this stage, you are just wanting to set up the initial developers. Later on, you will want to create a unique MongoDB user for each person and application that accesses the system. At a certain point, it will become far easier to create roles that define the exact access a set of users needs, following the principle of least privilege. Users then just need to be assigned the roles they need to perform their operations.
We use the mongo utility for this. The script is actually JavaScript, not PowerShell.
<# We will create a user administrator first, and then the God-like Database Admin then use the user administrator to create additional users. Create a unique MongoDB user for each person and application that accesses the system. A user can be a person or a client application. #> # In the admin database, add a user with the userAdminAnyDatabase role. mongo 'admin' --quiet --eval @" db=db.getSiblingDB('admin'); db.createUser( { user: '$UserAdmin', pwd: '$UserAdminPassword', roles: [ { role: 'userAdminAnyDatabase', db: 'admin' } ] } ); db.createUser( { user: '$Admin', pwd: '$AdminPassword', roles: [ 'userAdminAnyDatabase', 'readWriteAnyDatabase', 'dbAdminAnyDatabase', 'clusterAdmin' ] } ); "@
At this point, use the admin user and password to authenticate yourself, and list out the default databases, just to make sure it works.
mongo --quiet --host "$Mongohost" --port "$MongoPort" -u "`"$Admin`"" -p "`"$AdminPassword`"" --authenticationDatabase "admin" --eval "db.adminCommand('listDatabases')"
Good? You should be seeing a JSON list of default databases. You can now change the config file to change authorization: “disabled“, to authorization: “enabled”, stop the service and restart it.
Testing out remote connection with authentication
Now, having got this far, we need to log in remotely. This can be done via Studio 3T, or via PowerShell. If we are using PowerShell it has become apparent that at this point mongo is getting a lot of parameters. We can tidy this up in a couple of different ways.
The –host parameter takes a connection string, which combines four or more parameters into one.
$connectionString="mongodb://$($Admin):$($AdminPassword)@$($Mongohost):$($MongoPort)/admin?readPreference=primary"
So this will now work
mongo --quiet --host $connectionString --eval "db.adminCommand('listDatabases')"
I prefer to do it by ‘splatting’ an array. Normally, you ’splat’ a hashtable but with this sort of command-line app you can also do it for an array. Basically, we are creating an array that defines a connection, but it works for the other parameters.
$p=@('--quiet', '--host',"$Mongohost", '--port',"$MongoPort", '-u',"`"$Admin`"", '-p',"`"$AdminPassword`"", '--authenticationDatabase','"admin"', '--eval')
Now, we can use mongo much more tidily:
$Databases=(mongo $p "db.adminCommand('listDatabases')")|ConvertFrom-JSON $Databases.databases|format-table
You can, of course, alter the value of a parameter by changing the string array. Here we change the port while leaving everything else as-is.
$p.IndexOf('--port')|foreach{if ($_ -ne -1){$p[$_+1]='12234'}}
With our admin powers, we can now create a database, add a collection and insert a document into it.
# create the database by first accessing it .... $db = (mongo @p "dbnew=db.getSiblingDB('deleteMe')") # ... and now creating a collection (normally we'd want to create collations etc) $success = (mongo $db @p "db.createCollection('sample');") | ConvertFrom-JSON if ($success.ok -ne 0) # if that went well ... { # write a document to the collection we have just created mongo $db @p "db.sample.save( { 'item': 'book', 'qty': 40 } )"; #and now read it back out $result = ((mongo $db @p "db.sample.find().pretty().shellPrint()") ` -ireplace 'ObjectId\("(?[\w]{1,50})"\)', '"${innerds}"') | ConvertFrom-JSON } else #save the resulting document as a PowerShell object { write-warning $success.errmsg } $result #and display what we got back
We now should be basking in the glow of having achieved a setup.
From now on, the machine will be able to start the service after a reboot. You will need no further PowerShell scripting until, perhaps, an upgrade. Then the service has to be stopped and the pathname of the service altered to the new version.
Depending on the backward compatibility and other changes, you may hit other problems, but with these PowerShell commands you will be well-placed to fix any problems that come up.
Conclusions
It is always a good idea with MongoDB to upgrade regularly, implement authentication and run MongoDB from a server.
Because MongoDB can be a vulnerability when installed on a network, it is best to start off with reasonably good habits. By using PowerShell, the process of installing a Windows-based MongoDB server can become much easier because it gives you repeatability; and provides you with all the tools to do the job in one place.