
Author: murphyslaw4267
A Turkey Feast!
Azure!
I tried moving this website to CentOS 8 but I somehow managed to break YUM/DNF in my initial configuration of the LAMP stack. I don’t want to admit I screwed up so I’ll just say that CentOS 8 is not ready for production quite yet, at least not in my world. As a result of the remediation I have moved this server to Azure. I’m learning more and more about Microsoft’s way of running a cloud provider..
duck l’orange

Duck L’orange on Royal Carribean
Arepa! (and empanada)
CI/CD
I learn best by doing. I had heard continuous integration and continuous deployment thrown around for a while, often as buzz words. My current employer doesn’t really do either of these though we do use Jenkins as a deployment server. I’ve never seen it in action but from what I understand it has hooks into some of our java container hosts like WebLogic. It copies files over and understands compilation errors. Again I’ve never actually used it so this is just my understanding from what I’ve heard at work. GitLab gives you 2000 CI minutes which I never used. With my learning of Go it and pushing my code into it I started getting template suggestions for testing. The template provided needed a few edits and it started compiling my code and testing it for errors. It would place the built binary in a kind of hidden directory. It would do this on every push and took about 5 minutes. It used environment variables to protect credentials being in the config file.
Deployment was more difficult. I had trouble finding a good example out there on how to deploy my web app written in Go. Finally I found an alright example with a shell script that copied files. I augmented it with a service created on my web server.
Here’s my gitlab-ci.yml:
# This file is a template, and might need editing before it works on your project. image: golang:latest variables: # Please edit to your GitLab project REPO_NAME: gitlab.com/murphyslaw4267/cool_go_app APP_NAME: cool_go_app S3_BUCKET_NAME: "serverhobbyistohio" AWS_ACCESS_KEY_ID: $AWSID AWS_SECRET_ACCESS_KEY: $AWSSecret # The problem is that to be able to use go get, one needs to put # the repository in the $GOPATH. So for example if your gitlab domain # is gitlab.com, and that your repository is namespace/project, and # the default GOPATH being /go, then you'd need to have your # repository in /go/src/gitlab.com/namespace/project # Thus, making a symbolic link corrects this. before_script: - mkdir -p $GOPATH/src/$(dirname $REPO_NAME) - ln -svf $CI_PROJECT_DIR $GOPATH/src/$REPO_NAME - cd $GOPATH/src/$REPO_NAME stages: - test - build - deploy format: stage: test script: - go get github.com/aws/aws-sdk-go - go fmt $(go list ./... | grep -v /vendor/) - go vet $(go list ./... | grep -v /vendor/) - go test -race $(go list ./... | grep -v /vendor/) compile: stage: build script: - go get github.com/aws/aws-sdk-go - echo $CI_PROJECT_DIR - go build -race -ldflags "-extldflags '-static'" -o $CI_PROJECT_DIR/build/$APP_NAME artifacts: paths: - build/ deploy: stage: deploy only: - master image: ubuntu before_script: - apt update -y - apt install rsync -y - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client git -y )' - eval $(ssh-agent -s) - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null - mkdir -p ~/.ssh - chmod 700 ~/.ssh - ssh-keyscan webapp1.serverhobbyist.com >> ~/.ssh/known_hosts - chmod 644 ~/.ssh/known_hosts - rsync -ae ssh ./build/* root@webapp1.serverhobbyist.com:/var/www/go/cool_go_app/ - rsync -ae ssh ./*.html root@webapp1.serverhobbyist.com:/var/www/go/cool_go_app/ script: - bash .gitlab-deploy.sh
Learning About Lambda
Amazon Web Service’s serverless compute has always fascinated me. A lot of my pursuits of learning the DevOps methodology involves scripting but not full blown programming. When it comes to automation I can accomplish my goals with scripting. Now I just found out that you can run PowerShell in AWS Lambda but I think that is pretty recent. So before I knew that I decided I wanted to re-engage my brain centers for learning a programming language. I see a lot of hype and support for Google’s Go language. I started going through the tutorials and creating things. An idea came to me for a creation which I will talk about in a later post. For now I wanted to share my understanding of Lambda.
Lamba uses requests and events. The request contains the data it needs to function in kind of a HTTP post, at least that’s what it reminds me of since you can call it through a HTTP request and the Lambda library pulls out headers and the body. Then it returns in an HTTP fashion as well. Here’s a sample piece of code that leverages AWS Simple Email Service to take a name out of a HTTP body.
package main import ( "errors" "fmt" "log" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/ses" ) const ( // Replace sender@example.com with your "From" address. // This address must be verified with Amazon SES. Sender = "lambda@serverhobbyist.net" // Replace recipient@example.com with a "To" address. If your account // is still in the sandbox, this address must be verified. Recipient = "Recipient@Recipient.com" // Specify a configuration set. To use a configuration // set, comment the next line and line 92. //ConfigurationSet = "ConfigSet" // The subject line for the email. Subject = "Lambda Function Go!" // The character encoding for the email. CharSet = "UTF-8" ) var ( // ErrNameNotProvided is thrown when a name is not provided ErrNameNotProvided = errors.New("no name was provided in the HTTP body") ) // Handler is your Lambda function handler // It uses Amazon API Gateway request/responses provided by the aws-lambda-go/events package, // However you could use other event sources (S3, Kinesis etc), or JSON-decoded primitive types such as 'string'. func Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { var TextBody = "Hello " + request.Body var HtmlBody = "Hello " + request.Body sess, err := session.NewSession(&aws.Config{ Region: aws.String("us-east-1"), Credentials: credentials.NewStaticCredentials("Removed", "Removed", ""), }) // Create an SES session. svc := ses.New(sess) // Assemble the email. input := &ses.SendEmailInput{ Destination: &ses.Destination{ CcAddresses: []*string{}, ToAddresses: []*string{ aws.String(Recipient), }, BccAddresses: []*string{ aws.String(BCCRecipient), }, }, Message: &ses.Message{ Body: &ses.Body{ Html: &ses.Content{ Charset: aws.String(CharSet), Data: aws.String(HtmlBody), }, Text: &ses.Content{ Charset: aws.String(CharSet), Data: aws.String(TextBody), }, }, Subject: &ses.Content{ Charset: aws.String(CharSet), Data: aws.String(Subject), }, }, Source: aws.String(Sender), // Uncomment to use a configuration set //ConfigurationSetName: aws.String(ConfigurationSet), } // Attempt to send the email. result, err := svc.SendEmail(input) log.Println(result) // Display error messages if they occur. if err != nil { if aerr, ok := err.(awserr.Error); ok { switch aerr.Code() { case ses.ErrCodeMessageRejected: fmt.Println(ses.ErrCodeMessageRejected, aerr.Error()) case ses.ErrCodeMailFromDomainNotVerifiedException: fmt.Println(ses.ErrCodeMailFromDomainNotVerifiedException, aerr.Error()) case ses.ErrCodeConfigurationSetDoesNotExistException: fmt.Println(ses.ErrCodeConfigurationSetDoesNotExistException, aerr.Error()) default: fmt.Println(aerr.Error()) } } else { // Print the error, cast err to awserr.Error to get the Code and // Message from an error. fmt.Println(err.Error()) } } // stdout and stderr are sent to AWS CloudWatch Logs log.Printf("Processing Lambda request %s\n", request.RequestContext.RequestID) // If no name is provided in the HTTP request body, throw an error if len(request.Body) < 1 { return events.APIGatewayProxyResponse{}, ErrNameNotProvided } log.Println(request.PathParameters) return events.APIGatewayProxyResponse{ Body: "Hello " + request.Body, StatusCode: 200, }, nil } func main() { lambda.Start(Handler) }
Monitoring with Grafana
Monitoring is one of those things I’ve seen get overlooked. Throwing money at it doesn’t always make it better. Taking the time to sit down and understand and doing proper event management is key. Of course I say all of that and I don’t always practice it, especially on my own personal infrastructure. I think I go beyond the basics however. Originally I would just set up nagios and get emails and texts when a server didn’t respond to ping. Finding something that can monitor and alert on performance was more challenging. I ended up settling using the telegraf agent and having it send to an influxdb instance. Then grafana interprets the influxdb data and makes it into pretty visuals.

Its Raw!
Docker Swarm Limitations
As I continue to head towards my goal of using a container orchestration tool to be able to scale this website behind a load balancer I’m learning all kinds of things about the pitfalls involved with scalable systems. I though Docker Swarm would be great for this since it is relatively straight forward to set up but I’ve discovered it has a few limitations.
First of all it has no mechanism to scale the container hosts. I started down the path of scripting that and it was fairly successful at adding hosts to a swarm but then I learned another limiter. It doesn’t seem to have any way to balance containers among swarm workers at least after the initial start of the service. That means in a two worker node configuration you could have two container instances running on the same node. I know there are health checks that would in essence ‘heal’ your application but it seems silly to have an unused server out there. It was a smaller issue but persistent volumes wouldn’t update quite right even when you created them in line. If there was another volume with the same name on one of the worker nodes it would not give any errors and use that volumes settings and paths on that node. It was difficult to troubleshoot what was happening on that one.
Now I move towards the popular alternative, Kubernetes. Its popularity right now makes sense for me to figure out how it works. I’ve already stood up some basic services on a hosted cluster. It seems like an extra challenge to set it up from scratch. Thinking about my goals lately I wonder if I want to support this website on a complex set up long term. I want to learn it and know it but it may not make financial and logistical sense. So I think now my plan is to set it up and see how making changes is, otherwise simple one server setups are in my future for my personal assets.