Nowadays successful applications often consist of containers and container management system to: 1) ease scaling 2) reduce down time 3) add self healing capabilities. Let’s take a high level overview of most commonly used tools and build something cool too. My plan is to create open tcp port scanning tool. Use GO and worker pool to make it very fast. Expose it via REST resource, containerise, deploy and scale using kubernetes. For this example I’ll use my favourite project structure. You can find more about it . Let’s review the REST server: here main ( ) { cfg := cfg.New() srv := server. New(). WithAddr(cfg.GetAPIPort()). WithRouter(router.Get(cfg)). WithErrLogger(cfg.Errlog) { cfg.Infolog.Printf( , cfg.GetAPIPort()) err := srv.Start(); err != { cfg.Errlog.Printf( , err) } }() gracefulshutdown.Init(cfg.Errlog, { err := srv.Close(); err != { cfg.Errlog.Printf( , err) } }) } // cmd/server/main.go package import "github.com/port-scanner/cmd/server/cfg" "github.com/port-scanner/cmd/server/router" "github.com/port-scanner/pkg/gracefulshutdown" "github.com/port-scanner/pkg/server" func main () // configure server instance // start server in separate goroutine so that it doesn't block graceful // shutdown handler go func () "starting server at %s" if nil "starting server: %s" // initiate graceful shutdown handler that will listen to api crash signals // and perform cleanup func () if nil "closing server: %s" I created an instance of package that will be used to hold program wide configuration and will ease the DI (dependency injection). This usually is a good place to parse any command line arguments, assemble and hold DB connection details and more. cfg I then create and configure reusable instance by passing port, and to it. Server is started in separate go routine so I could initiate a service to handle program exits and perform cleanup if needed. server router logger graceful shutdown I will only have one end-point. Let’s review the handler: GET open-ports handlers ( ) openPorts { FromPort ToPort Domain OpenPorts [] } { r.Body.Close() w.Header().Add( , ) queryValues := r.URL.Query() domain := queryValues.Get( ) toPort := queryValues.Get( ) v := reqvalidator.New() v.Required( , domain) v.Required( , toPort) v.ValidDecimalString( , toPort) !v.Valid() { w.WriteHeader(http.StatusForbidden) w.Write(v.GetErrResp()) } port, _ := strconv.Atoi(toPort) op := portscanner. New(domain). ScanTo(port) report := openPorts{ FromPort: , ToPort: port, Domain: domain, OpenPorts: op, } resp, err := json.Marshal(report) err != { w.WriteHeader(http.StatusInternalServerError) } w.WriteHeader(http.StatusOK) w.Write(resp) } // cmd/server/handlers/scanports.go package import "encoding/json" "net/http" "strconv" "github.com/julienschmidt/httprouter" "github.com/port-scanner/pkg/portscanner" "github.com/port-scanner/pkg/reqvalidator" type struct int `json:"from_port"` int `json:"to_port"` string `json:"domain"` int `json:"open_ports"` func ScanOpenPorts (w http.ResponseWriter, r *http.Request, _ httprouter.Params) defer "Content-Type" "application/json" "domain" "toPort" "domain" "toPort" "toPort" if return // safe to skip error check here as validator above has done that already 0 if nil return I expect to receive few query params in this handler: and . Both params are mandatory and must be valid decimal string representation. I have validator in place to check exactly that. The number will represent the port limit that my scanner will check and also define the size of worker pool. This will allow the scanner to perform all calls at once. domain toPort toPort toPort Before going any further, let’s review the validator: reqvalidator ( ) validationerrors [ ][] { ve[field] = (ve[field], message) } errResp { Result Cause InvalidFields validationerrors } ReqValidator { Errors validationerrors } { ReqValidator{ validationerrors( [ ][] {}), } } { strings.TrimSpace(value) == { rv.Errors.Add(field, ) } } { _, err := strconv.Atoi(value) err != { rv.Errors.Add(field, ) } } { (rv.Errors) == } { er := errResp{ Result: , Cause: , InvalidFields: rv.Errors, } b, _ := json.Marshal(er) b } // pkg/reqvalidator/reqvalidator.go package import "encoding/json" "strconv" "strings" type map string string func (ve validationerrors) Add (field, message ) string append type struct string string type struct func New () ReqValidator return map string string func (rv ReqValidator) Required (field, value ) string if "" "can't be blank" func (rv ReqValidator) ValidDecimalString (field, value ) string if nil "invalid decimal string" func (rv ReqValidator) Valid () bool return len 0 [] func (rv ReqValidator) GetErrResp () byte "ERROR" "INVALID_REQUEST" return and are my basic validations. Given the field name and value, I keep track of error messages if validation fails. Call to checks to see if there're any errors and if errors are present I can retrieve them with and send that information to consumer. Required ValidDecimalString Valid GetErrResp Now let’s review the scanner: portscanner ( ) Scanner { domain } { Scanner{domain} } { jobs := ( ) results := ( ) i := ; i < toPort; i++ { worker(s.domain, jobs, results) } { i := ; i <= toPort; i++ { jobs <- i } }() i := ; i <= toPort; i++ { port := <-results port != { openPorts = (openPorts, port) } } (jobs) (results) openPorts } { j := jobs { _, err := net.DialTimeout( , fmt.Sprintf( , domain, j), *time.Second) err != { results <- } results <- j } } // pkg/portscanner/portscanner.go package import "fmt" "net" "time" type struct string func New (domain ) string Scanner return func (s Scanner) ScanTo (toPort ) int (openPorts [] ) int make chan int make chan int for 0 go go func () for 1 for 1 if 0 append close close return func worker (domain , jobs <- , results <- ) string chan int chan int for range "tcp" "%s:%d" 2 if nil 0 continue The function does the heavy lifting here. I first create and channels. I then spawn workers. Each worker will be receiving port over channel, scan that port and return result of the TCP call over channels. I use so that worker does not wait for response for longer than 2 seconds. When last port is finished scanning I return all open ports to consumer. ScanTo jobs results jobs results net.DialTimeout You can already try interacting with this tool by running: $ go run cmd/server/main.go And issuing curl commands for localhost:8080/open-ports I hope you have learned something useful. In next post I'll go through steps needed to containerise this app, deploy and scale using k8s. You can find the source code . here