You absolutely can write code in modern OpenGL style while at the same time restricting yourself to OpenGL 2.1 (or lower) calls.
All that you need to do is avoid using calls or features that are marked deprecated, which typically (for OpenGL 2.1) means using shaders and vertex buffers for all drawing.
That's OK because shaders and vertex buffers are not modern OpenGL features. They're actually very old features, with shaders being made core in OpenGL 2.0 (and available as extensions before then) and vertex buffers being even older, being made core in OpenGL 1.5 and likewise being available as extensions before then.
Other deprecated features you'll need to avoid include using the built-in matrix stack and built-in GLSL input uniforms and attributes, but that's also OK because even OpenGL 2.0 has always supported alternatives to them: use your own matrix library and provide your own uniforms and attributes.
So for example, the following is perfectly valid OpenGL 2.1 code:
Some GL 3.0+ features you will (probably; they may be available as extensions) not be able to use include:
- Geometry shaders.
- Uniform buffer objects.
- Sampler objects.
- Floating-point texture formats.
- Framebuffer objects.
This is also OK, because there's nothing about OpenGL 3.3 that requires you to use these.
As an alternative, software implementations do exist, but I would personally consider them unsuitable (on grounds of poor performance) for GameDev work.