It has to do with Kubernetes controllers & CustomResourceDefinition.
When you write an operator for Kubernetes, you would define custom objects. For example, Elastic (www.elastic.co) has an operator deploying ElasticSearch/Kibana. They decided to call one of their object "elasticsearch".
Those resource names are arbitrary, and nothing guarantee there won't be someone else, working on its own operator, using the same names. Thus, when you define your custom resources, you would also affect them with an API group.
The API group is also arbitrary, though would usually identify a unique controller/operator/set of functionality. In the case of the ElasticSearch operator, they went with an API group "k8s.elastic.co". When installing this operator CRD on your cluster, a kubectl get crd would list objects such as "elasticsearchs.k8s.elastic.co", "kibanas.k8s.elastic.co".
You could very well deploy another operator, which implements its own "elasticsearch" or "kibana" objects, within its own API group.
Thus, when you write your Roles / ClusterRoles, that apiGroup field is crucial configuring your RBAC. The resources array lists short names, the apiGroup is then used resolving the fully qualified CRD name.
While kubernetes legacy objects use some empty string, other native objects may use networking.k8s.io, batch, extensions, ... In the meantime, custom controllers would all use their own api group, whenever they rely on their own CRDs.
Now, I'm not sure I know the answer to 'why I can use "" and "events.k8s.io" together?'. I would say it's historic?
I did not see any cases where setting multiple api groups in a rule was benefic. In my opinion, it's misleading, a source of mistake, ... when you find a rule with two api groups, you can be pretty sure the person who wrote it did not understand what they were doing.
At best, one combination of apigroup / resource grants what you meant to. Worst case, one combination of apigroup / resource grants something you did not want to allow in the first place.
Erratum: events are an exception, though. As pointed out in some kubernetes GitHub Issue ( https://github.com/kubernetes/kubernetes/issues/94857 ), some member would argue this is not a bug: we really need both API groups. Which is kind of surprising.
Not sure why would that be. Sidenote, the schema for Event in core/v1 and events.k8s.io isn't the same:
https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#event-v1-events-k8s-io
https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#event-v1-core
So they're not the same object strictly speaking, although from what I can see, they return the same data / in terms of etcd, it's the same data, with two JSON representations depending on which api group you query:
$ k get event.events.k8s.io -n kube-system
LAST SEEN TYPE REASON OBJECT MESSAGE
58m Warning Unhealthy pod/metrics-server-7df46996d8-c5wf5 Readiness probe failed: Get "https://10.233.108.212:8443/healthz": context deadline exceeded
...
$ k get event -n kube-system
LAST SEEN TYPE REASON OBJECT MESSAGE
58m Warning Unhealthy pod/metrics-server-7df46996d8-c5wf5 Readiness probe failed: Get "https://10.233.108.212:8443/healthz": context deadline exceeded
...
Not sure what's the rational here. events.k8s.io isn't anything new, I haven't read about some transition from one to the other ... That's a good question you have.