Deploying a Server Written in Go on Linux

Deployment Context and Preferences

Recently, I deployed a REST server written in Go. The target system is a Linux box running CentOS 7. The service is managed using systemd. Logrotate is configured to rotate the server’s logs on a daily basis. I couldn’t find any “how-to” that provided step-by-step instructions for configuring services written in Go. This is an attempt to document how to set up a simple service written in Go. It’s the result of poking around and chatting with people who know a lot more than me on this subject. It’s my hope that someone finds it useful. For bonus points, there’s a link to Ansible scripts at the end of the post that automate the tasks outlined here. One thing to note is that most commands are run as the root user.

1) - Copy Server Artifacts

For me, right now, deployment of artifacts written in Go involves copying binaries and configuration files over to a server. That may change in the future but for now, it’s that simple. For the purposes of this documentation, I’m going to use a small executable called simple-api (64-bit Linux Binary) as an example binary to deploy. This executable is the server software for a small REST API. It has one endpoint which responds to GET requests. The executable, along with Ansible scripts to automate deployment, can be found in the GitHub repository link at the end of this post.

To deploy the executable, I copy it in my server’s /opts directory under a sub-directory tree called ./redsofa/simple-api. The final directory structure for this simple service looks like : /opt/redsofa/simple-api. The executable and a configuration file for the service are copied in this directory. That’s it for deployment. The rest of the deployment tasks involves creating a user to run the service and configuring it.

2) - Create a User to Run Service

To be secure, I create a user to run my service. For this post I’m going to create a user called gosrvc. The user is tagged as a system user (-r option) without a home directory (-M option) and is not meant to login to the system (-s option).

The command to create the user is as follows :

adduser -r -M -s /bin/false gosrvc

3) - File System Permissions

I make sure that the gosrvc user and group owns everything (recursive) under the /opt/redsofa directory.

The command to do that looks like :

chown -R gosrvc:gosrvc /opt/redsofa

I also make sure that file permissions are restrictive. The commands to do that look like :

find /opt/redsofa -type f -exec chmod 600 {} \;
find /opt/redsofa -type d -exec chmod 700 {} \;
chmod 500 /opt/redsofa/simple-api/simple-api

These permissions may be a little overkill. My current opinion on permissions is not to give users access to something if they don’t need it. However, it’s important to be practical so using good judgment in context is key.

4) - Create systemd Unit File

In systemd, a unit refers to a resource that the system knows about. A unit is used to abstract services, network resources, devices, etc. These resources are defined using configuration files called unit files. I create a unit file called simple-api.service in the /usr/lib/systemd/system directory to define my Go REST service.

The unit file looks like :

[Unit]
Description=Simple API server written in Go

[Service]
Type=simple
User=gosrvc
Group=gosrvc
WorkingDirectory=/opt/redsofa/simple-api
ExecStart=/bin/bash -c '/opt/redsofa/simple-api/simple-api >> simple-api.log 2>&1'
Restart=always

[Install]
WantedBy=multi-user.target

The thing to note about the ExecStart configuration line above is that stdout and stderr streams are both stored to the simple-api.log file. This trick enables us to persist logged events from our simple-api server to a file.

5) - Enable and Start Service

The command to enable and start the service is as follows :

systemctl enable simple-api.service
systemctl start simple-api.service

6) - Log Rotation

In order to have logrotate automatically rotate the logs for the service, I create a configuration file called /etc/logrotate.d/simple-api. The contents of that file looks like :

/opt/redsofa/simple-api/simple-api.log {
    missingok
    notifempty
    compress
    size 20k
    daily
    create 0600 gosrvc gosrvc
    su
    copytruncate
}

To test that log rotation is working properly, this command can be used :

logrotate -f /etc/logrotate.conf

Note that the command above will rotate all system logs. Keep this “potential issue” in mind if you decide to run the command. Once the command is run, you should see that your log has been rotated in the /opt/redsofa/simple-api/ directory.

Testing The Sample Service

To test whether our service has started properly, I use the following command :

systemctl status simple-api

The output of the command should look like :

Figure 1 - systemctl status call result

To test the service locally (on the deployment target box), A curl call can be made like this :

curl \
--verbose \
--silent \
--insecure \
--request GET \
--header "Content-Type: application/json" \
  http://localhost:8080/simple-api

The output of the curl command should look like :

Figure 2 - curl call to localhost on port 8080

At this point, the service is deployed. There are proxy or firewall configurations that could be done to expose the service to the outside world. However, I’m not going to talk about this here.

Ansible

Ansible is great for automating system administration tasks. What I like about Ansible is that with this tool, managing servers looks more like a process that resembles software development than anything else. Automation scripts can be checked into source code management and the result of running these scripts is more consistent and predictable than following documentation and executing commands manually. I also like that Ansible doesn’t require any special software running on the machine you are administering.

At some point in the future, I plan to explore Ansible a little further. I may look at using it as a tool in a continuous deployment context. For now, here is a link to tasks outlined in this post captured as a set of runnable Ansible scripts. Enjoy!

Source files for automating the steps outlined in this post : GitHub Repository

comments powered by Disqus