So you want to expose Go on the Internet
blog.gopheracademy.com
blog.gopheracademy.com
https://grisha.org/blog/2014/06/03/graceful-restart-in-golan...
BTW - to people new to Go this article may make it look like serving HTTP is complicated, but it's actually remarkably easy. And if you consider that it is actually _possible_ to have a complete server with just the standard lib (and with TLS and HTTP/2 to boot) running as a single process - compared to Python or Ruby, where because of the GIL you _must_ place an apache/nginx/haproxy in front of it and also run a bunch of unicorns or something similar on different ports (at which point you need something like Chef/Puppet to manage the config because it gets very complicated very fast) - this is actually pretty amazing.
https://github.com/golang/go/issues/4674
Agree that serving http/https with Go is really pretty straightforward - you can do it without all the tweaks in this article, and most people have been. It works remarkably well by default and scales well too without much effort.
or you know, just call fork() a few times, like Apache has done for more than a decade
Calling fork() internally is ultimately akin to what everyone is going to do. Why not use a library to do this and let that code gradually get tempered to a reliable status?
1. Start a new process, start accepting new connections
2. In your old process, stop accepting new connections — old ones are still active.
3. In the old process, close old connections when they become idle.
4. In the old process, wait until all of your connections are closed and then quit or automatically quit after a timeout.
Also, assuming that any static assets are served, probably a good idea to leverage sendfile.
Not necessarily:
setcap 'cap_net_bind_service=+ep' your_go_binary
./your_go_binaryAnother option would be to drop privileges at runtime.
virtualenvs don't create a new interpreter, they just fudge the python path?
Definitely not recommended on interpreted languages (although we use it all the time on our go apps).
AmbientCapabilities=CAP_NET_BIND_SERVICE
otherwise you'll have to run setcap any time the binary changes.
Go already leverages sendfile:
By comparison, app code is often developed rapidly and often only reviewed by at most a few people. Even companies staffed by brilliant minds like Google regularly have vulnerabilities in their application code.
People need to stop hype the buzzword GIL. NodeJS/GoLang doesnt even support native threading.
Is goroutines very effective with CPU-bound computation tasks?
The specific thing I was dealing with when I wrote the blog post wasn't high-volume enough for it to make any difference, so I don't really know....
But is there a better option than a WaitGroup here?
Now that you have multiple processes, you need something to dispatch between them - intro Nginx/Gunicorn etc.
A single Go process can exploit all the system resources available without the multi-process orchestration required by Python/Ruby and thus does not require the extra layer above.
That's an implementation detail of your Python/Ruby program. I've written plenty of Ruby apps that have been multi-process and multi-threaded, and both at the same time.
> Now that you have multiple processes, you need something to dispatch between them
Yes, that's the case irrespective of language.
> A single Go process can exploit all the system resources available without the multi-process orchestration required by Python/Ruby and thus does not require the extra layer above.
This is no different than for Ruby at least.
Your criticisms are mostly outdated as of Ruby 1.9.x, though you can get bitten by the GIL. (EDIT: to clarify, avoiding this depends on either multi-process or taking advantage of the fact that while Ruby Thread's can only be scheduled one at a time, C-extensions etc. that releases the GIL can run in parallel with other Ruby Thread's; of course all of this is MRI specific in any case)
In practice, if you use a reasonably modern web server like Puma, you get good concurrency without having to think much about it.
I don't need gunicorn or nginx in front of my Java/Go/C# process to achieve concurrency and take advantage of all CPU cores.
>> Your criticisms are mostly outdated as of Ruby 1.9.x
Python 3.x & Ruby 1.9.x have the same limitations - any native code will lock the GIL, so unless all your code is in extensions you're going to run into this problem. Ruby 1.9 introducing OS threads did not change this.
Sure it's great that I can write an extension in C and take advantage of multiple OS threads in my Ruby/Python runtime, but the moment those threads have to deal with ANY native objects you're back to square one.
Just because you are using stdlib not an external application does not change the functionality that is happening.
Do you also think that Java is multi-process? I can see the argument that an OS thread is a process but it is basically irrelevant in every way that matters in development and operations.
An example - how do you access the same shared-memory (say, a map/dictionary) from multiple cores in any particular runtime?
- Go: you make a map, you protect the map with a mutex, and you reference it directly.
- Java: same, or ConcurrentHashMap or whatever is available.
- 'Worker'/'forking' runtimes: you can't, right? You move the state to another component (such as Redis), or you use an OS-provided shared memory facility, or you need inter-process RPC etc ...
--
Of course, sometimes workers being separate processes is a virtue rather then a burden ... but I'm not sure this is true in the case of Ruby or Python when it's more purely a limitation of the runtime.
Performance & failsafe is a big part of the appeal, but so is local caching, traffic splitting (for A/B testing or regional versions), etc. It's hard to ignore that when choosing to expose your server directly or put it behind NGINX.
Unless you use none of these things, you'll end up reinventing a bunch of wheels.
I'm still not sure what the advantage of that is over using perhaps the most reliable, certainly most-used web server as a proxy in front of your app. I'm open to convincing, though.
I don't mean to overstate the advantages--I think both solutions are fine; neither will make or break your operation.
I use go net/http every day, in production. I trust it, but NGINX provides so much more battle tested functionality out of the box.
How do you know how many lines are required to configure hypothetical middleware?
> No go gets
How is static compilation worse than `apt-get install` or `docker run`?
> No middleware
It's another process... why would running another process be better than middleware?
> it's well tested, mature and backed by software that powers a majority of the web.
Granted. It seems like this is the only clear win for NGINX, and it may well change if Go libraries mature. Time will tell.
Sorry but I'm not going to be writing ansible code that modifies structs inside of some program then compiles said program. No thank you that sounds like crazy town. Also other sysadmins and infrastructure engineers will actually know how things work and won't have to go reading the source code for some crazy Go app program at 2am that also is a webserver for some reason?
Separation of concerns, use it!!
The discussion is scoped to a single Go application. No one is proposing replacing NGINX with Go (or anything else) for JVM apps.
> won't have to go reading the source code for some crazy Go app program at 2am that also is a webserver for some reason?
This is a rephrasing of the question I posed earlier--is it easier to manage configuration in Go source code or NGINX config files.
> Separation of concerns, use it!!
Concerns can be separated without being in distinct processes or implemented by distinct programmers or implemented in distinct programming languages.
Primarily through experience. Happy to be shown otherwise. Middleware usually takes a bit more configuration than that.
> Granted. It seems like this is the only clear win for NGINX, and it may well change if Go libraries mature. Time will tell.
We can quibble over whether it's the "only" win, but even so, it's a very, very big win. It's a single point of failure that handles a lot of features you'd have to replicate with various packages that lack the kind of support, maturity and community that NGINX has.
It's also about reproducing all the business logic provided by nginx, not just configuration. You can't pretend like net/http package gives you everything ngnix provides, that's a lie.
When it comes to these decisions, I use the Principal of Least Power (https://en.wikipedia.org/wiki/Rule_of_least_power)
If writing
proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m max_size=10g
location / {
proxy_cache my_cache;
proxy_pass http://my_upstream;
}
gets me what I want without Turing-completeness, I'll choose that.Obviously, more complicated tasks require more complicated tools.
This doesn't seem like a solid basis for making engineering decisions -- or any kind of decisions.
This doesn't always lead to the best decisions -- although by chance it can work out okay -- and it also has the effect of limiting your horizons. Now I can't say one should learn about every single web server out there, but when the choice is between a dedicated web server and a library in a particular programming language, there is really a lot to see on the other side. Rewrite rules and the wide variety of HTTP stats available for logging are both areas where web servers can give us new ideas.
When I was much younger I thought, why learn databases? I can use Java, I know XML. Just open the XML file and add the records. There's XQuery for searching... The historical precursor for "Why not write everything in Go?" is "Why not write everything in Java?".
I think you can know enough about an alternative to decide whether or not its appropriate without conquering its learning curve.
> I think you can know enough about an alternative to decide whether or not its appropriate without conquering its learning curve.
Yeah, but that's not the same thing as using your level of knowledge as the deciding factor.
Correct me if I misunderstood you, but you don't want _engineers_ who write your code to have access to private TLS keys which are _used_ in production
For one thing, a malicious or disgruntled engineer could sneak in code that exposes your private key material in some fashion.
Secondly, and more likely, your engineers may make a mistake that inadvertently exposes process memory, which would include your private key material.
In simpler terms, it often pays to have a firewall between important company secrets and the guy/gal who happens to be working on your web app this month.
Assuming you would answer the question "Do your engineers have root access to production machines?" in the negative, you probably also don't want your engineers writing code that has access to the things that the root user has access to.
One other way to put this is, are you comfortable running your Go process as root (in order to bind to port 443)?
You do not need to run as root to bind an application to a low port, instead use setcap (it works for everything not just Go): https://stackoverflow.com/questions/14537045/how-i-should-ru...
If you compromise a process, you can potentially exfiltrate its memory. You'd need to also compromise the operating system to exfiltrate memory from other processes.
So, keys being in nginx means you can only get the keys by breaking nginx (or the OS), not by breaking the in-house application.
Also, try to avoid passing in keys as command line arguments. If you can, avoid using environment variables, too. You can pass thet data in using standard in, so the data is never exposed.
Example of leaky environment variables:
https://gist.github.com/amorphid/db037f03246962959b6a034b2ca...
The unix permissions model is designed to isolate one user's data from another.
https://gist.github.com/amorphid/4a65741d14db38b96341d7e1f2d...
The short version is I'm passing a variable in via the pid's standard in, reading the line, and then declaring the variable. This is a very contrived example :) But you can write a wrapper script that would handle all of the line reading for you.
This originally came up when I was asking someone how to pass sensitive information (API keys, passwords, etc.). I did some research, and found this approach.
In most programming languages, for basic system calls, you basically just call a command, that command runs, and then exits. But sometimes you want a script that can take information from standard in, or send it to you from standard out. Like you may write a script that runs for a few minutes, then says "OK, I'm ready for the password!", and then you pass it in at the time it's needed (but honestly, don't do it unless you need to, because it's one more thing that can break.
Erlang/Elixir land have a library called erlexec that does this => http://saleyn.github.io/erlexec/
Another Elixir library is Porcelain => https://github.com/alco/porcelain
TBH, though, if they are sniffing env variables from processes there's not reason not to sniff that process's memory directly.
From engineers sure.
But a separate process helps for other threats, like heartbleed.
Even though Cowboy (and frameworks built atop it, like Phoenix) is known to perform well under load (including DDoS-like load), I've always been wary about exposing directly it to the Internet. I know NGINX was explicitly hardened against many classes of web server attack; I haven't ever seen the same claimed about Cowboy.
It'd be reassuring just to know of anyone with a large, public-facing web service, who has deployed Erlang in a directly-exposed HTTP server role, weathered attacks, and come out fine. (Heroku, maybe?) But I haven't heard much on that front, either.
(shamless plug) In terms of the TLS, I wrote a short blog post (http://blog.commando.io/the-perfect-nginx-ssl-configuration/) on a setting up NGINX to get an A+ rating on Qualys SSL Labs. It is really only a few lines/directives.
If it helps anyone, I published the recommendations here plus some other resources into a simple Go package for instant A+ server report: https://github.com/Xeoncross/secureserver
Also, the coverage of the various timeouts for HTTP requests is mostly new information to me. Is that something that nginx and apache usually take care of?
The difference is Nginx sets timeouts based on the time between successive byte reads or writes, so for example if you have a 30 second timeout and receive one byte every 29 seconds, you won't trigger Nginx's timeouts: https://kev.inburke.com/slides/reliable-http/#connect-timeou...
Go generally prefers wall-clock timeouts for reading or writing the entire response, that is, if you don't get the whole thing in 30 seconds, return an error, regardless of when you received each individual byte. Although you can configure Nginx-style timeouts if you want.
I've worked with more than one company handling over 100k requests/s on the public internet with Go. Go's networking model combined with the work that's gone into fuzzing the stdlib combined with the benefit of hindsight when it comes to data structures and security combined with lots of love from google web people has resulted in an extremely mature web stack.
Since these connections are long lived, and normal connections are generally short, the total number of connections gets dominated by the slow ones. If no other mitigations are put into place, then this can cause a server to hit ulimits/max_conns and keep legitimate requests blocked.
This is known as the "Slowloris" attack[3], and is mentioned in the nginx DDoS mitigation blog post[4].
[0] - http://nginx.org/en/docs/http/ngx_http_upstream_module.html#...
[1] - http://nginx.org/en/docs/http/ngx_http_core_module.html#clie...
[2] - http://nginx.org/en/docs/http/ngx_http_core_module.html#clie...
[3] - https://en.wikipedia.org/wiki/Slowloris_(computer_security)
[4] - https://www.nginx.com/blog/mitigating-ddos-attacks-with-ngin...
The only thing missing is an equivalent of the "Idle" timeout mentioned in the OP: I'm curious how you think it should behave in the HTTP 1.1 pipelining case, I guess only counting the time that the connection is totally idle?
It seems like it shouldn't be this hard.
They are constrained to some extent by the Go 1 compatibility promise, for example they don't want to change default behaviour on timeouts probably because of that. Hopefully if they have a Go 2 at some point they'll use that opportunity to clean up some APIs and fix a few things like this.
http://serverfault.com/questions/112795/how-to-run-a-server-...
Has many good answers.
I always have an Nginx proxy in front of services. Whether it's Go, Ruby or Node (or Docker)
For resilience, HA, proxy routing, static files, masking backend errors, caching.
Does HTTP+S and 2 and Websocket so nicely.
Nginx all the things.
Anything you do in your service other than your own business logic is reinventing the wheel (I mean in the HTTP level), and not doing it so well as someone before you (nginx/HaProxy etc...).
This is a generalization of course, but my strategy of putting Nginx in front of everything didn't fail me so far.
some questions that come to mind, i also leverage nginx for static file caching, i've seen some sample code for fileserver in net/http, but what kind of algorithm does fileserver use for caching, lru? can you configure the size of the cache?
and in terms of scale, i haven't reached this point yet in my project, but from the _olden_ sinatra days, i'd spin up multiple processes and proxy through nginx. in terms of a single (machine) server, could a go process essentially be limited to 1 per server? i'm assuming the go binary could leverage multiple cores automatically so i wouldn't need to do like ruby or python?
what are your experiences with go backend services? i run a restful api server that connects to a database and redis, so far, performance seems good enough where i only need 1 go process per machine.
I wonder what he feels about using Caddy instead of bare net/http ?
Both are battle tested(HAProxy more so), and like another poster said...if you don't use something like them, you're going to re-invent the wheel in several areas.
https://dennisforbes.ca/index.php/2013/08/07/ten-reasons-you...
-and in re-analysis it all holds completely true. Nginx gives my deploy flexibility, at essentially negligible cost. And no Go development should include a bunch of boilerplate code to do banal stuff like serving static content.
http.ListenAndServe(":8080", http.FileServer(http.Dir("/usr/share/doc"))
https://godoc.org/net/http#FileServerIf I could get there, I'd have full control over every aspect of an API request from packet to server query, back to payload, in a single programming language, in a single conceptual framework. There is a lot to like about that. I'm not sure it's needed - nginx works so well, and perfect settings are just a single config file away. But if I could get to a truly single-binary deployment, I'd be pretty happy too.
> Back when crypto/tls was slow and net/http young, the general wisdom was to always put Go servers behind a reverse proxy like NGINX. That’s not necessary anymore!
Second, the point isn't to select crypto that "works", but rather to select crypto that is efficiently supported by Golang.
Sounds more like he's giving advice to others on the finer points of elliptic curve cryptography. Programmers should not need to know this stuff.
The key word being unoptimized. In the article, the code snippet has a comment "Only use curves which have assembly implementations" and he mentions that "a client using CurveP384 would cause up to a second of CPU to be consumed on our machines." (presumably because it does not have an assembly implementation)
> Programmers should not need to know this stuff.
It can sometimes be a sign of a leaky abstraction, but programmers might need to know the performance characteristics of the code they write.
In case you actually care to discuss it, there's definitely a trade-off between variety of cipher support (which some people want) and efficiency of cipher support (which some people absolutely need). Prioritizing one over the other is not a clear or simple decision.
type CurveID uint16
const (
CurveP256 CurveID = 23
CurveP384 CurveID = 24
CurveP521 CurveID = 25
)